Merge pull request #757 from Microsoft/vbulavin/issue_719
#719 - Implemented
This commit is contained in:
Коммит
2ab39e7182
|
@ -57,7 +57,7 @@
|
|||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Spotlight-40@2x-1.png",
|
||||
"filename" : "Icon-Spotlight-42.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
|
|
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 3.6 KiB |
Двоичные данные
EmbeddedSocial-Example/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-42.png
Normal file
Двоичные данные
EmbeddedSocial-Example/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-42.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.5 KiB |
|
@ -876,7 +876,7 @@
|
|||
88C661311FB46D4C0096B776 /* ReplyUpdateHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C661301FB46D4C0096B776 /* ReplyUpdateHint.swift */; };
|
||||
88C661331FB4708E0096B776 /* Reply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C661321FB4708E0096B776 /* Reply.swift */; };
|
||||
88C661351FB478690096B776 /* TopicUpdateHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C661341FB478690096B776 /* TopicUpdateHint.swift */; };
|
||||
88C661371FB481FF0096B776 /* HandleChangesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C661361FB481FF0096B776 /* HandleChangesManager.swift */; };
|
||||
88C661371FB481FF0096B776 /* HandleChangesMulticast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C661361FB481FF0096B776 /* HandleChangesMulticast.swift */; };
|
||||
88C665061F866B0800A7763D /* PopularUsersAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C665051F866B0800A7763D /* PopularUsersAPI.swift */; };
|
||||
88C665081F866E8500A7763D /* PopularUsersResponseProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C665071F866E8500A7763D /* PopularUsersResponseProcessor.swift */; };
|
||||
88C6650A1F8671B200A7763D /* PopularUsersAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C665091F8671B200A7763D /* PopularUsersAPITests.swift */; };
|
||||
|
@ -917,6 +917,9 @@
|
|||
88D123261F753BF6001523D1 /* MockOutgoingCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D123251F753BF6001523D1 /* MockOutgoingCommand.swift */; };
|
||||
88D123281F753D5D001523D1 /* MockOutgoingCommandOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D123271F753D5D001523D1 /* MockOutgoingCommandOperation.swift */; };
|
||||
88D1232A1F754278001523D1 /* OperationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D123291F754278001523D1 /* OperationObserver.swift */; };
|
||||
88D291DD1FB61C990007879F /* HandleUpdateDaemon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D291DC1FB61C990007879F /* HandleUpdateDaemon.swift */; };
|
||||
88D291DF1FB61DF20007879F /* DaemonsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D291DE1FB61DF20007879F /* DaemonsFactory.swift */; };
|
||||
88D291E11FB61E010007879F /* DaemonsFactoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D291E01FB61E010007879F /* DaemonsFactoryImpl.swift */; };
|
||||
88D2F8D71F55C2B50004A8BC /* ReportingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D2F8D61F55C2B50004A8BC /* ReportingService.swift */; };
|
||||
88D2F8D91F56A4440004A8BC /* ReportSubmittedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D2F8D81F56A4440004A8BC /* ReportSubmittedViewController.swift */; };
|
||||
88D2F8DB1F56A61B0004A8BC /* ReportRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D2F8DA1F56A61B0004A8BC /* ReportRouter.swift */; };
|
||||
|
@ -1046,6 +1049,7 @@
|
|||
88F551D61F3AF8BE00F4AAB9 /* UserFollowersAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F551D51F3AF8BE00F4AAB9 /* UserFollowersAPITests.swift */; };
|
||||
88F551D81F3AF93600F4AAB9 /* MyFollowingAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F551D71F3AF93600F4AAB9 /* MyFollowingAPITests.swift */; };
|
||||
88F551DA1F3AFA8700F4AAB9 /* UserFollowingAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F551D91F3AFA8700F4AAB9 /* UserFollowingAPITests.swift */; };
|
||||
88F6F9921FB6066100802BA9 /* CacheRequestExecutorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F6F9911FB6066100802BA9 /* CacheRequestExecutorProvider.swift */; };
|
||||
88F7A7861F2788DE005FEC5F /* LoginInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F7A7851F2788DE005FEC5F /* LoginInteractorTests.swift */; };
|
||||
88F7A7891F27896B005FEC5F /* MockAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F7A7881F27896B005FEC5F /* MockAuthService.swift */; };
|
||||
88F7A78B1F2789F0005FEC5F /* MockUserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F7A78A1F2789F0005FEC5F /* MockUserService.swift */; };
|
||||
|
@ -2179,7 +2183,7 @@
|
|||
88C661301FB46D4C0096B776 /* ReplyUpdateHint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyUpdateHint.swift; sourceTree = "<group>"; };
|
||||
88C661321FB4708E0096B776 /* Reply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reply.swift; sourceTree = "<group>"; };
|
||||
88C661341FB478690096B776 /* TopicUpdateHint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicUpdateHint.swift; sourceTree = "<group>"; };
|
||||
88C661361FB481FF0096B776 /* HandleChangesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleChangesManager.swift; sourceTree = "<group>"; };
|
||||
88C661361FB481FF0096B776 /* HandleChangesMulticast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleChangesMulticast.swift; sourceTree = "<group>"; };
|
||||
88C665051F866B0800A7763D /* PopularUsersAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopularUsersAPI.swift; sourceTree = "<group>"; };
|
||||
88C665071F866E8500A7763D /* PopularUsersResponseProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopularUsersResponseProcessor.swift; sourceTree = "<group>"; };
|
||||
88C665091F8671B200A7763D /* PopularUsersAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopularUsersAPITests.swift; sourceTree = "<group>"; };
|
||||
|
@ -2222,6 +2226,9 @@
|
|||
88D123251F753BF6001523D1 /* MockOutgoingCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockOutgoingCommand.swift; sourceTree = "<group>"; };
|
||||
88D123271F753D5D001523D1 /* MockOutgoingCommandOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockOutgoingCommandOperation.swift; sourceTree = "<group>"; };
|
||||
88D123291F754278001523D1 /* OperationObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationObserver.swift; sourceTree = "<group>"; };
|
||||
88D291DC1FB61C990007879F /* HandleUpdateDaemon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleUpdateDaemon.swift; sourceTree = "<group>"; };
|
||||
88D291DE1FB61DF20007879F /* DaemonsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaemonsFactory.swift; sourceTree = "<group>"; };
|
||||
88D291E01FB61E010007879F /* DaemonsFactoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaemonsFactoryImpl.swift; sourceTree = "<group>"; };
|
||||
88D2F8D61F55C2B50004A8BC /* ReportingService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportingService.swift; sourceTree = "<group>"; };
|
||||
88D2F8D81F56A4440004A8BC /* ReportSubmittedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportSubmittedViewController.swift; sourceTree = "<group>"; };
|
||||
88D2F8DA1F56A61B0004A8BC /* ReportRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportRouter.swift; sourceTree = "<group>"; };
|
||||
|
@ -2351,6 +2358,7 @@
|
|||
88F551D51F3AF8BE00F4AAB9 /* UserFollowersAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserFollowersAPITests.swift; sourceTree = "<group>"; };
|
||||
88F551D71F3AF93600F4AAB9 /* MyFollowingAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyFollowingAPITests.swift; sourceTree = "<group>"; };
|
||||
88F551D91F3AFA8700F4AAB9 /* UserFollowingAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserFollowingAPITests.swift; sourceTree = "<group>"; };
|
||||
88F6F9911FB6066100802BA9 /* CacheRequestExecutorProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheRequestExecutorProvider.swift; sourceTree = "<group>"; };
|
||||
88F7A7851F2788DE005FEC5F /* LoginInteractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginInteractorTests.swift; sourceTree = "<group>"; };
|
||||
88F7A7881F27896B005FEC5F /* MockAuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockAuthService.swift; sourceTree = "<group>"; };
|
||||
88F7A78A1F2789F0005FEC5F /* MockUserService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockUserService.swift; sourceTree = "<group>"; };
|
||||
|
@ -3233,6 +3241,7 @@
|
|||
884CDD841FACA2B8000D467B /* OutgoingExecutors */,
|
||||
88BE87441FACA1D300F5AF05 /* IncomingExecutors */,
|
||||
8802F2191F596BBA00FD0A15 /* RequestExecutorProvider.swift */,
|
||||
88F6F9911FB6066100802BA9 /* CacheRequestExecutorProvider.swift */,
|
||||
);
|
||||
path = RequestExecuters;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3878,12 +3887,12 @@
|
|||
88580A871F2213AB00C1CBAF /* Base */,
|
||||
88303DDB1F8771BB00330E30 /* PaginatedListProcessor */,
|
||||
88580A951F2213AB00C1CBAF /* SideMenu */,
|
||||
88F6F9971FB61A7A00802BA9 /* Publisher-Subscriber */,
|
||||
88580A8B1F2213AB00C1CBAF /* APIError.swift */,
|
||||
88580A8C1F2213AB00C1CBAF /* AppFonts.swift */,
|
||||
880435CB1F67D04500154ACD /* AsyncOperation.swift */,
|
||||
8831EDE71F975CF10013C8B8 /* CacheCleanupStrategy.swift */,
|
||||
88580A8A1F2213AB00C1CBAF /* CellModel.swift */,
|
||||
88C6612C1FB44F2E0096B776 /* Publisher-Subscriber.swift */,
|
||||
88B94EF91F66B278002392F9 /* Daemon.swift */,
|
||||
9CDDB2021F38866000C46785 /* DateFormatter.swift */,
|
||||
88580A8D1F2213AB00C1CBAF /* Identifiable.swift */,
|
||||
|
@ -3891,9 +3900,6 @@
|
|||
88580A8F1F2213AB00C1CBAF /* LaunchArguments.swift */,
|
||||
636474921F628715000D7B3D /* MenuViewModelBuilder.swift */,
|
||||
8814291F1F4B19F400FC9F1E /* MyProfileOpener.swift */,
|
||||
88C6612E1FB455B40096B776 /* CommentUpdateHint.swift */,
|
||||
88C661341FB478690096B776 /* TopicUpdateHint.swift */,
|
||||
88C661301FB46D4C0096B776 /* ReplyUpdateHint.swift */,
|
||||
9C70FCD31F9A3DEE00BEB047 /* NotificationsUpdater.swift */,
|
||||
88580A901F2213AB00C1CBAF /* NavigationStack.swift */,
|
||||
88580A921F2213AB00C1CBAF /* Palette.swift */,
|
||||
|
@ -4040,8 +4046,9 @@
|
|||
88F551CA1F39E7F700F4AAB9 /* AutoEquatable.swift */,
|
||||
32EBE7791F9FD7920007EF53 /* FeedPresentBatchModel.swift */,
|
||||
88580B031F2213AB00C1CBAF /* CrossModuleCoordinator.swift */,
|
||||
88C661361FB481FF0096B776 /* HandleChangesManager.swift */,
|
||||
88C661361FB481FF0096B776 /* HandleChangesMulticast.swift */,
|
||||
882AD0C21F2225FF00FC20B7 /* QueriableRepository.swift */,
|
||||
88D291DC1FB61C990007879F /* HandleUpdateDaemon.swift */,
|
||||
88580C7C1F2213EF00C1CBAF /* Repository.swift */,
|
||||
9CB26BF41F989B54005B9797 /* ActivityNotificationsController.swift */,
|
||||
);
|
||||
|
@ -4939,6 +4946,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
8881E4E31F6976C300AA6DF2 /* DaemonsController.swift */,
|
||||
88D291DE1FB61DF20007879F /* DaemonsFactory.swift */,
|
||||
88D291E01FB61E010007879F /* DaemonsFactoryImpl.swift */,
|
||||
88580B251F2213AB00C1CBAF /* SocialMenuItemsProvider.swift */,
|
||||
88580B2B1F2213AB00C1CBAF /* SocialPlus.swift */,
|
||||
88580B261F2213AB00C1CBAF /* SocialPlusServiceProvider.swift */,
|
||||
|
@ -5904,6 +5913,17 @@
|
|||
path = Followers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88F6F9971FB61A7A00802BA9 /* Publisher-Subscriber */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
88C6612E1FB455B40096B776 /* CommentUpdateHint.swift */,
|
||||
88C661341FB478690096B776 /* TopicUpdateHint.swift */,
|
||||
88C661301FB46D4C0096B776 /* ReplyUpdateHint.swift */,
|
||||
88C6612C1FB44F2E0096B776 /* Publisher-Subscriber.swift */,
|
||||
);
|
||||
path = "Publisher-Subscriber";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88F7A7871F2788E1005FEC5F /* Login */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -6829,7 +6849,7 @@
|
|||
TargetAttributes = {
|
||||
886B39C41F138F5D00BF1A8E = {
|
||||
CreatedOnToolsVersion = 8.3.3;
|
||||
ProvisioningStyle = Manual;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
8EF0E5911F1FA5CC00E88ED6 = {
|
||||
CreatedOnToolsVersion = 8.3.3;
|
||||
|
@ -6838,7 +6858,7 @@
|
|||
};
|
||||
9CD805281F0D012F00B9B0AC = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
ProvisioningStyle = Manual;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
9CD805311F0D012F00B9B0AC = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
|
@ -7270,7 +7290,7 @@
|
|||
88580C501F2213AB00C1CBAF /* LoginInteractorInput.swift in Sources */,
|
||||
8811C85E1F42FB6B00F7513D /* EmbeddedEditProfileInteractorInput.swift in Sources */,
|
||||
9C6D35A41F31D1C1005893DB /* FeedModuleViewOutput.swift in Sources */,
|
||||
88C661371FB481FF0096B776 /* HandleChangesManager.swift in Sources */,
|
||||
88C661371FB481FF0096B776 /* HandleChangesMulticast.swift in Sources */,
|
||||
88580BBC1F2213AB00C1CBAF /* String+Encoding.swift in Sources */,
|
||||
88580C4D1F2213AB00C1CBAF /* CreatePostViewOutput.swift in Sources */,
|
||||
8846CB491F30C98C00F11EC8 /* UserListModuleInput.swift in Sources */,
|
||||
|
@ -7315,6 +7335,7 @@
|
|||
88204FBB1F581F2A00E15F5A /* SuggestedUsersRequestExecutor.swift in Sources */,
|
||||
88F385861F30D1080048B04C /* UserListInteractorInput.swift in Sources */,
|
||||
88BC5C501F2B65D0001FBDD2 /* UserProfileInteractorInput.swift in Sources */,
|
||||
88D291DD1FB61C990007879F /* HandleUpdateDaemon.swift in Sources */,
|
||||
88F3857F1F30CD890048B04C /* UserListModuleOutput.swift in Sources */,
|
||||
88580C4E1F2213AB00C1CBAF /* LoginConfigurator.swift in Sources */,
|
||||
88B94F061F66D01E002392F9 /* UnfollowUserOperation.swift in Sources */,
|
||||
|
@ -7332,6 +7353,7 @@
|
|||
885E04021F6FBFB600D6EBD8 /* CommentCommand.swift in Sources */,
|
||||
88B94EFA1F66B278002392F9 /* Daemon.swift in Sources */,
|
||||
88E3AC0B1F55567F0016357F /* SettingsViewOutput.swift in Sources */,
|
||||
88D291DF1FB61DF20007879F /* DaemonsFactory.swift in Sources */,
|
||||
885994DF1F96586300947544 /* RemoveTopicCommand.swift in Sources */,
|
||||
8864C8221F45BA6000ADCE13 /* SearchViewController.swift in Sources */,
|
||||
8811C8881F431B9A00F7513D /* EditProfileViewInput.swift in Sources */,
|
||||
|
@ -7603,6 +7625,7 @@
|
|||
88C682551F7A4D82004BD291 /* FollowRequestsInteractorInput.swift in Sources */,
|
||||
9CB26BF51F989B54005B9797 /* ActivityNotificationsController.swift in Sources */,
|
||||
8864C8501F4709D300ADCE13 /* SearchTabInfo.swift in Sources */,
|
||||
88F6F9921FB6066100802BA9 /* CacheRequestExecutorProvider.swift in Sources */,
|
||||
883BFFCF1F71124000E1C4E3 /* CreateTopicImageCommand.swift in Sources */,
|
||||
883234451F9F7E050008DAD2 /* LiveOperationProgress.m in Sources */,
|
||||
88E3AC461F55B7570016357F /* ReportViewInput.swift in Sources */,
|
||||
|
@ -7712,6 +7735,7 @@
|
|||
88580C711F2213AB00C1CBAF /* TabMenuContainerViewInput.swift in Sources */,
|
||||
8832345F1F9F7E050008DAD2 /* URL+OAuthSwift.swift in Sources */,
|
||||
88580BB01F2213AB00C1CBAF /* TableDataDisplayManager.swift in Sources */,
|
||||
88D291E11FB61E010007879F /* DaemonsFactoryImpl.swift in Sources */,
|
||||
8892F25B1F7E618500A58D8B /* LinkedAccountView+Ext.swift in Sources */,
|
||||
8875CCE71F331CB3001F2474 /* FollowingConfigurator.swift in Sources */,
|
||||
88BC5C5B1F2B6628001FBDD2 /* ProfileSummaryView.swift in Sources */,
|
||||
|
@ -8237,9 +8261,9 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEVELOPMENT_TEAM = 9KBH5RKYEW;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 285W24646W;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
INFOPLIST_FILE = "EmbeddedSocial-Example/Info.plist";
|
||||
|
@ -8247,8 +8271,8 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.microsoft.embeddedsocial-df";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "369c1331-48f6-48ab-908c-42ec1f7a638a";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Microsoft Embedded Social Dogfood Distribution";
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
|
@ -8260,9 +8284,9 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEVELOPMENT_TEAM = 9KBH5RKYEW;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 285W24646W;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
INFOPLIST_FILE = "EmbeddedSocial-Example/Info.plist";
|
||||
|
@ -8270,8 +8294,8 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.microsoft.embeddedsocial-df";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "369c1331-48f6-48ab-908c-42ec1f7a638a";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Microsoft Embedded Social Dogfood Distribution";
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
|
@ -8430,8 +8454,9 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 9KBH5RKYEW;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
|
@ -8474,8 +8499,9 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 9KBH5RKYEW;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
struct CommentUpdateHint: Hint {
|
||||
struct CommentUpdateHint: HandleUpdateHint {
|
||||
let oldHandle: String
|
||||
let newHandle: String
|
||||
}
|
|
@ -17,3 +17,8 @@ protocol Subscriber: class {
|
|||
protocol Hint {
|
||||
|
||||
}
|
||||
|
||||
protocol HandleUpdateHint: Hint {
|
||||
var oldHandle: String { get }
|
||||
var newHandle: String { get }
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
struct ReplyUpdateHint: Hint {
|
||||
struct ReplyUpdateHint: HandleUpdateHint {
|
||||
let oldHandle: String
|
||||
let newHandle: String
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
struct TopicUpdateHint: Hint {
|
||||
struct TopicUpdateHint: HandleUpdateHint {
|
||||
let oldHandle: String
|
||||
let newHandle: String
|
||||
}
|
|
@ -49,4 +49,9 @@ extension CacheType {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
func isCached(_ command: OutgoingCommand) -> Bool {
|
||||
let p = PredicateBuilder().predicate(for: command)
|
||||
return firstOutgoing(ofType: OutgoingCommand.self, predicate: p, sortDescriptors: nil) != nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,19 @@ class CommentCommand: OutgoingCommand {
|
|||
|
||||
}
|
||||
|
||||
func apply(to feed: inout CommentFetchResult) {
|
||||
var comments = feed.comments
|
||||
|
||||
for (index, var comment) in comments.enumerated() {
|
||||
if comment.commentHandle == self.comment.commentHandle {
|
||||
apply(to: &comment)
|
||||
comments[index] = comment
|
||||
}
|
||||
}
|
||||
|
||||
feed.comments = comments
|
||||
}
|
||||
|
||||
override func encodeToJSON() -> Any {
|
||||
return [
|
||||
"comment": comment.encodeToJSON(),
|
||||
|
@ -74,4 +87,8 @@ class CommentCommand: OutgoingCommand {
|
|||
ReportCommentCommand.self
|
||||
]
|
||||
}
|
||||
|
||||
var isActionCommand: Bool {
|
||||
return CommentCommand.commentActionCommandTypes.contains(where: { $0 == type(of: self) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,12 @@ final class CreateCommentCommand: CommentCommand {
|
|||
override func getRelatedHandle() -> String? {
|
||||
return comment.topicHandle
|
||||
}
|
||||
|
||||
override func apply(to feed: inout CommentFetchResult) {
|
||||
var comments = feed.comments
|
||||
comments.insert(comment, at: 0)
|
||||
feed.comments = comments
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,4 +18,12 @@ final class RemoveCommentCommand: CommentCommand {
|
|||
override func getRelatedHandle() -> String? {
|
||||
return comment.topicHandle
|
||||
}
|
||||
|
||||
override func apply(to feed: inout CommentFetchResult) {
|
||||
var comments = feed.comments
|
||||
if let index = comments.index(where: { $0.commentHandle == self.comment.commentHandle }) {
|
||||
comments.remove(at: index)
|
||||
}
|
||||
feed.comments = comments
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,4 +18,23 @@ final class CreateReplyCommand: ReplyCommand {
|
|||
override func setRelatedHandle(_ relatedHandle: String?) {
|
||||
reply.commentHandle = relatedHandle
|
||||
}
|
||||
|
||||
func apply(to feed: inout CommentFetchResult) {
|
||||
var comments = feed.comments
|
||||
|
||||
for (index, comment) in comments.enumerated() {
|
||||
if comment.commentHandle == reply.commentHandle {
|
||||
comment.totalReplies += 1
|
||||
comments[index] = comment
|
||||
}
|
||||
}
|
||||
|
||||
feed.comments = comments
|
||||
}
|
||||
|
||||
override func apply(to feed: inout RepliesFetchResult) {
|
||||
var replies = feed.replies
|
||||
replies.insert(reply, at: 0)
|
||||
feed.replies = replies
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,4 +18,12 @@ final class RemoveReplyCommand: ReplyCommand {
|
|||
override func getRelatedHandle() -> String? {
|
||||
return reply.commentHandle
|
||||
}
|
||||
|
||||
override func apply(to feed: inout RepliesFetchResult) {
|
||||
var replies = feed.replies
|
||||
if let index = replies.index(where: { $0.replyHandle == self.reply.replyHandle }) {
|
||||
replies.remove(at: index)
|
||||
}
|
||||
feed.replies = replies
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,19 @@ class ReplyCommand: OutgoingCommand {
|
|||
|
||||
}
|
||||
|
||||
func apply(to feed: inout RepliesFetchResult) {
|
||||
var replies = feed.replies
|
||||
|
||||
for (index, var reply) in replies.enumerated() {
|
||||
if reply.replyHandle == self.reply.replyHandle {
|
||||
apply(to: &reply)
|
||||
replies[index] = reply
|
||||
}
|
||||
}
|
||||
|
||||
feed.replies = replies
|
||||
}
|
||||
|
||||
override func encodeToJSON() -> Any {
|
||||
return [
|
||||
"reply": reply.encodeToJSON(),
|
||||
|
@ -64,4 +77,8 @@ class ReplyCommand: OutgoingCommand {
|
|||
ReportReplyCommand.self
|
||||
]
|
||||
}
|
||||
|
||||
var isActionCommand: Bool {
|
||||
return ReplyCommand.replyActionCommandTypes.contains(where: { $0 == type(of: self) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,30 +16,30 @@ struct OutgoingCommandOperationsBuilder: OutgoingCommandOperationsBuilderType {
|
|||
func operation(for command: OutgoingCommand) -> OutgoingCommandOperation? {
|
||||
// user commands
|
||||
if let command = command as? FollowCommand {
|
||||
return FollowUserOperation(command: command, socialService: SocialService())
|
||||
return FollowUserOperation(command: command, socialService: makeSocialService())
|
||||
} else if let command = command as? UnfollowCommand {
|
||||
return UnfollowUserOperation(command: command, socialService: SocialService())
|
||||
return UnfollowUserOperation(command: command, socialService: makeSocialService())
|
||||
} else if let command = command as? BlockCommand {
|
||||
return BlockUserOperation(command: command, socialService: SocialService())
|
||||
return BlockUserOperation(command: command, socialService: makeSocialService())
|
||||
} else if let command = command as? UnblockCommand {
|
||||
return UnblockUserOperation(command: command, socialService: SocialService())
|
||||
return UnblockUserOperation(command: command, socialService: makeSocialService())
|
||||
} else if let command = command as? CancelPendingCommand {
|
||||
return CancelPendingUserOperation(command: command, socialService: SocialService())
|
||||
return CancelPendingUserOperation(command: command, socialService: makeSocialService())
|
||||
} else if let command = command as? AcceptPendingCommand {
|
||||
return AcceptPendingUserOperation(command: command, socialService: SocialService())
|
||||
return AcceptPendingUserOperation(command: command, socialService: makeSocialService())
|
||||
} else if let command = command as? ReportUserCommand {
|
||||
return ReportUserOperation(command: command, service: ReportingService())
|
||||
}
|
||||
|
||||
// topic commands
|
||||
else if let command = command as? LikeTopicCommand {
|
||||
return LikeTopicOperation(command: command, likesService: LikesService())
|
||||
return LikeTopicOperation(command: command, likesService: makeLikesService())
|
||||
} else if let command = command as? UnlikeTopicCommand {
|
||||
return UnlikeTopicOperation(command: command, likesService: LikesService())
|
||||
return UnlikeTopicOperation(command: command, likesService: makeLikesService())
|
||||
} else if let command = command as? PinTopicCommand {
|
||||
return PinTopicOperation(command: command, likesService: LikesService())
|
||||
return PinTopicOperation(command: command, likesService: makeLikesService())
|
||||
} else if let command = command as? UnpinTopicCommand {
|
||||
return UnpinTopicOperation(command: command, likesService: LikesService())
|
||||
return UnpinTopicOperation(command: command, likesService: makeLikesService())
|
||||
} else if let command = command as? CreateTopicCommand {
|
||||
return CreateTopicOperation(command: command, topicsService: TopicService(imagesService: ImagesService()))
|
||||
} else if let command = command as? RemoveTopicCommand {
|
||||
|
@ -54,9 +54,9 @@ struct OutgoingCommandOperationsBuilder: OutgoingCommandOperationsBuilderType {
|
|||
|
||||
// reply commands
|
||||
else if let command = command as? LikeReplyCommand {
|
||||
return LikeReplyOperation(command: command, likesService: LikesService())
|
||||
return LikeReplyOperation(command: command, likesService: makeLikesService())
|
||||
} else if let command = command as? UnlikeReplyCommand {
|
||||
return UnlikeReplyOperation(command: command, likesService: LikesService())
|
||||
return UnlikeReplyOperation(command: command, likesService: makeLikesService())
|
||||
} else if let command = command as? CreateReplyCommand {
|
||||
return CreateReplyOperation(command: command, repliesService: RepliesService())
|
||||
} else if let command = command as? RemoveReplyCommand {
|
||||
|
@ -69,9 +69,9 @@ struct OutgoingCommandOperationsBuilder: OutgoingCommandOperationsBuilderType {
|
|||
|
||||
// comment commands
|
||||
else if let command = command as? LikeCommentCommand {
|
||||
return LikeCommentOperation(command: command, likesService: LikesService())
|
||||
return LikeCommentOperation(command: command, likesService: makeLikesService())
|
||||
} else if let command = command as? UnlikeCommentCommand {
|
||||
return UnlikeCommentOperation(command: command, likesService: LikesService())
|
||||
return UnlikeCommentOperation(command: command, likesService: makeLikesService())
|
||||
} else if let command = command as? CreateCommentCommand {
|
||||
return CreateCommentOperation(command: command, commentsService: CommentsService(imagesService: ImagesService()))
|
||||
} else if let command = command as? RemoveCommentCommand {
|
||||
|
@ -104,6 +104,14 @@ struct OutgoingCommandOperationsBuilder: OutgoingCommandOperationsBuilderType {
|
|||
return nil
|
||||
}
|
||||
|
||||
private func makeLikesService() -> LikesServiceProtocol {
|
||||
return LikesService()
|
||||
}
|
||||
|
||||
private func makeSocialService() -> SocialServiceType {
|
||||
return SocialService()
|
||||
}
|
||||
|
||||
func fetchCommandsOperation(cache: CacheType, predicate: NSPredicate) -> FetchOutgoingCommandsOperation {
|
||||
return FetchOutgoingCommandsOperation(cache: cache, predicate: predicate)
|
||||
}
|
||||
|
|
|
@ -191,6 +191,20 @@ extension PredicateBuilder: OutgoingCommandsPredicateBuilder {
|
|||
func allNotificationCommands() -> NSPredicate {
|
||||
return NSPredicate(format: "typeid = %@", UpdateNotificationsStatusCommand.typeIdentifier)
|
||||
}
|
||||
|
||||
func allCommentCommands() -> NSPredicate {
|
||||
let typeIDs = CommentCommand.allCommentCommandTypes.map { $0.typeIdentifier }
|
||||
return NSPredicate(format: "typeid IN %@", typeIDs)
|
||||
}
|
||||
|
||||
func allReplyCommands() -> NSPredicate {
|
||||
let typeIDs = ReplyCommand.allReplyCommandTypes.map { $0.typeIdentifier }
|
||||
return NSPredicate(format: "typeid IN %@", typeIDs)
|
||||
}
|
||||
|
||||
func createReplyCommands() -> NSPredicate {
|
||||
return NSPredicate(format: "typeid = %@", CreateReplyCommand.typeIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
extension PredicateBuilder: TopicsFeedProcessorPredicateBuilder {
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
class HandleChangesManager: Publisher {
|
||||
class HandleChangesMulticast: Publisher {
|
||||
|
||||
static let shared = HandleChangesManager()
|
||||
static let shared = HandleChangesMulticast()
|
||||
|
||||
private let multicast = MulticastDelegate<Subscriber>()
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class HandleUpdateDaemon: Daemon, Subscriber {
|
||||
|
||||
private let multicast: HandleChangesMulticast
|
||||
private let predicateBuilder = PredicateBuilder()
|
||||
private let handleUpdater: HandleUpdater
|
||||
|
||||
init(handleChangesMulticast: HandleChangesMulticast = HandleChangesMulticast.shared,
|
||||
handleUpdater: HandleUpdater = OutgoingCommandsHandleUpdater()) {
|
||||
|
||||
multicast = handleChangesMulticast
|
||||
self.handleUpdater = handleUpdater
|
||||
}
|
||||
|
||||
func start() {
|
||||
multicast.subscribe(self)
|
||||
}
|
||||
|
||||
func stop() { }
|
||||
|
||||
func update(_ hint: Hint) {
|
||||
guard let hint = hint as? HandleUpdateHint else { return }
|
||||
let p = predicateBuilder.predicate(handle: hint.oldHandle)
|
||||
handleUpdater.updateHandle(to: hint.newHandle, predicate: p)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class CacheRequestExecutorProvider: CacheRequestExecutorProviderType {
|
||||
|
||||
static func makeUsersFeedExecutor(for service: BaseService) -> UsersFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserCompactView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: UsersListResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeMyFollowingExecutor(for service: BaseService) -> UsersFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserCompactView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: MyFollowingResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeMyBlockedUsersExecutor(for service: BaseService) -> UsersFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserCompactView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: MyBlockedUsersResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeTopicsFeedExecutor(for service: BaseService) -> TopicsFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseTopicView.self,
|
||||
responseType: FeedFetchResult.self,
|
||||
service: service,
|
||||
responseProcessor: TopicsFeedResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeMyRecentTopicsFeedExecutor(for service: BaseService) -> TopicsFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseTopicView.self,
|
||||
responseType: FeedFetchResult.self,
|
||||
service: service,
|
||||
responseProcessor: MyRecentTopicsFeedResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeMyActivityExecutor(for service: BaseService) -> MyActivityRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseActivityView.self,
|
||||
responseType: PaginatedResponse<ActivityView>.self,
|
||||
service: service,
|
||||
responseProcessor: ActivityFeedProcessor())
|
||||
}
|
||||
|
||||
static func makeSinglePostExecutor(for service: BaseService) -> SingleTopicRequestExecutor {
|
||||
return makeCommonExecutor(requestType: TopicView.self,
|
||||
responseType: Post.self,
|
||||
service: service,
|
||||
responseProcessor: SinglePostResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeSuggestedUsersExecutor(for service: BaseService) -> SuggestedUsersRequestExecutor {
|
||||
let executor = SuggestedUsersRequestExecutorImpl()
|
||||
bind(service: service, to: executor)
|
||||
return executor
|
||||
}
|
||||
|
||||
class func makeAtomicOutgoingCommandsExecutor(for service: BaseService) -> AtomicOutgoingCommandsExecutor {
|
||||
let executor = AtomicOutgoingCommandsExecutorImpl()
|
||||
executor.cache = service.cache
|
||||
executor.errorHandler = service.errorHandler
|
||||
return executor
|
||||
}
|
||||
|
||||
static func makeMyFollowersExecutor(for service: BaseService) -> UsersFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserCompactView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: MyFollowersResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeMyPendingRequestsExecutor(for service: BaseService) -> UsersFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserCompactView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: PendingRequestsResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeSearchTopicsFeedExecutor(for service: BaseService) -> TopicsFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseTopicView.self,
|
||||
responseType: FeedFetchResult.self,
|
||||
service: service,
|
||||
responseProcessor: SearchTopicsFeedResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makePopularUsersExecutor(for service: BaseService) -> PopularUsersRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserProfileView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: PopularUsersResponseProcessor())
|
||||
}
|
||||
|
||||
static func makeHashtagsExecutor(for service: BaseService) -> HashtagsRequestExecutor {
|
||||
let executor = HashtagsRequestExecutorImpl()
|
||||
executor.cache = service.cache
|
||||
executor.errorHandler = service.errorHandler
|
||||
return executor
|
||||
}
|
||||
|
||||
private static func makeCommonExecutor<T: Cacheable, U>(
|
||||
requestType: T.Type,
|
||||
responseType: U.Type,
|
||||
service: BaseService,
|
||||
responseProcessor: ResponseProcessor<T, U>) -> IncomingCacheRequestExecutor<T, U> {
|
||||
|
||||
let executor = IncomingCacheRequestExecutorImpl<T, U>()
|
||||
bind(service: service, to: executor)
|
||||
executor.responseProcessor = responseProcessor
|
||||
return executor
|
||||
}
|
||||
|
||||
private static func bind<T, U>(service: BaseService, to executor: IncomingCacheRequestExecutor<T, U>) {
|
||||
executor.cache = service.cache
|
||||
executor.errorHandler = service.errorHandler
|
||||
executor.networkTracker = service.networkStatusMulticast
|
||||
}
|
||||
}
|
|
@ -15,9 +15,13 @@ class AtomicOutgoingCommandsExecutorImpl: OutgoingCacheRequestExecutor<Object, V
|
|||
builder: RequestBuilder<Object>,
|
||||
completion: @escaping (Result<Void>) -> Void) {
|
||||
|
||||
cacheAndComplete(command: command, completion: completion)
|
||||
runCommand(command, with: builder)
|
||||
}
|
||||
|
||||
func cacheAndComplete(command: OutgoingCommand, completion: @escaping (Result<Void>) -> Void) {
|
||||
cacheCommand(command)
|
||||
completion(.success())
|
||||
runCommand(command, with: builder)
|
||||
}
|
||||
|
||||
private func cacheCommand(_ command: OutgoingCommand) {
|
||||
|
@ -59,5 +63,4 @@ class AtomicOutgoingCommandsExecutorImpl: OutgoingCacheRequestExecutor<Object, V
|
|||
}
|
||||
commandBeingExecuted = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -47,120 +47,3 @@ protocol CacheRequestExecutorProviderType {
|
|||
static func makeHashtagsExecutor(for service: BaseService) -> HashtagsRequestExecutor
|
||||
}
|
||||
|
||||
struct CacheRequestExecutorProvider: CacheRequestExecutorProviderType {
|
||||
|
||||
static func makeUsersFeedExecutor(for service: BaseService) -> UsersFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserCompactView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: UsersListResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeMyFollowingExecutor(for service: BaseService) -> UsersFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserCompactView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: MyFollowingResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeMyBlockedUsersExecutor(for service: BaseService) -> UsersFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserCompactView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: MyBlockedUsersResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeTopicsFeedExecutor(for service: BaseService) -> TopicsFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseTopicView.self,
|
||||
responseType: FeedFetchResult.self,
|
||||
service: service,
|
||||
responseProcessor: TopicsFeedResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeMyRecentTopicsFeedExecutor(for service: BaseService) -> TopicsFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseTopicView.self,
|
||||
responseType: FeedFetchResult.self,
|
||||
service: service,
|
||||
responseProcessor: MyRecentTopicsFeedResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeMyActivityExecutor(for service: BaseService) -> MyActivityRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseActivityView.self,
|
||||
responseType: PaginatedResponse<ActivityView>.self,
|
||||
service: service,
|
||||
responseProcessor: ActivityFeedProcessor())
|
||||
}
|
||||
|
||||
static func makeSinglePostExecutor(for service: BaseService) -> SingleTopicRequestExecutor {
|
||||
return makeCommonExecutor(requestType: TopicView.self,
|
||||
responseType: Post.self,
|
||||
service: service,
|
||||
responseProcessor: SinglePostResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeSuggestedUsersExecutor(for service: BaseService) -> SuggestedUsersRequestExecutor {
|
||||
let executor = SuggestedUsersRequestExecutorImpl()
|
||||
bind(service: service, to: executor)
|
||||
return executor
|
||||
}
|
||||
|
||||
static func makeAtomicOutgoingCommandsExecutor(for service: BaseService) -> AtomicOutgoingCommandsExecutor {
|
||||
let executor = AtomicOutgoingCommandsExecutorImpl()
|
||||
executor.cache = service.cache
|
||||
executor.errorHandler = service.errorHandler
|
||||
return executor
|
||||
}
|
||||
|
||||
static func makeMyFollowersExecutor(for service: BaseService) -> UsersFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserCompactView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: MyFollowersResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeMyPendingRequestsExecutor(for service: BaseService) -> UsersFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserCompactView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: PendingRequestsResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makeSearchTopicsFeedExecutor(for service: BaseService) -> TopicsFeedRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseTopicView.self,
|
||||
responseType: FeedFetchResult.self,
|
||||
service: service,
|
||||
responseProcessor: SearchTopicsFeedResponseProcessor(cache: service.cache))
|
||||
}
|
||||
|
||||
static func makePopularUsersExecutor(for service: BaseService) -> PopularUsersRequestExecutor {
|
||||
return makeCommonExecutor(requestType: FeedResponseUserProfileView.self,
|
||||
responseType: UsersListResponse.self,
|
||||
service: service,
|
||||
responseProcessor: PopularUsersResponseProcessor())
|
||||
}
|
||||
|
||||
static func makeHashtagsExecutor(for service: BaseService) -> HashtagsRequestExecutor {
|
||||
let executor = HashtagsRequestExecutorImpl()
|
||||
executor.cache = service.cache
|
||||
executor.errorHandler = service.errorHandler
|
||||
return executor
|
||||
}
|
||||
|
||||
private static func makeCommonExecutor<T: Cacheable, U>(
|
||||
requestType: T.Type,
|
||||
responseType: U.Type,
|
||||
service: BaseService,
|
||||
responseProcessor: ResponseProcessor<T, U>) -> IncomingCacheRequestExecutor<T, U> {
|
||||
|
||||
let executor = IncomingCacheRequestExecutorImpl<T, U>()
|
||||
bind(service: service, to: executor)
|
||||
executor.responseProcessor = responseProcessor
|
||||
return executor
|
||||
}
|
||||
|
||||
private static func bind<T, U>(service: BaseService, to executor: IncomingCacheRequestExecutor<T, U>) {
|
||||
executor.cache = service.cache
|
||||
executor.errorHandler = service.errorHandler
|
||||
executor.networkTracker = service.networkStatusMulticast
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,68 +6,60 @@
|
|||
import Foundation
|
||||
|
||||
protocol CommentsProcessorType {
|
||||
func proccess(_ fetchResult: inout CommentFetchResult)
|
||||
func proccess(_ fetchResult: inout CommentFetchResult, topicHandle: String)
|
||||
}
|
||||
|
||||
class CommentsProcessor: CommentsProcessorType {
|
||||
|
||||
let cache: CacheType
|
||||
private let cache: CacheType
|
||||
private let predicateBuilder = PredicateBuilder()
|
||||
private var commands: [CommentCommand] = []
|
||||
|
||||
required init(cache: CacheType) {
|
||||
self.cache = cache
|
||||
}
|
||||
|
||||
func proccess(_ fetchResult: inout CommentFetchResult) {
|
||||
let commands = fetchCreateDeleteCommentCommands() + fetchCommentActionCommands()
|
||||
|
||||
for command in fetchCreateDeleteReplyCommands() {
|
||||
if let index = fetchResult.comments.index(where: { $0.commentHandle == command.reply.commentHandle }) {
|
||||
if command is RemoveReplyCommand {
|
||||
if fetchResult.comments[index].totalReplies > 0 {
|
||||
fetchResult.comments[index].totalReplies -= 1
|
||||
}
|
||||
} else if command is CreateReplyCommand {
|
||||
fetchResult.comments[index].totalReplies += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetchCreateDeleteCommentCommands().filter( { $0.self is RemoveCommentCommand } ) .forEach { (commands) in
|
||||
fetchResult.comments = fetchResult.comments.filter({ $0.commentHandle != commands.comment.commentHandle })
|
||||
}
|
||||
|
||||
fetchResult.comments = fetchResult.comments.map({ (comment) -> Comment in
|
||||
var comment = comment
|
||||
let commandsToApply = commands.filter { $0.comment.commentHandle == comment.commentHandle }
|
||||
for command in commandsToApply {
|
||||
command.apply(to: &comment)
|
||||
}
|
||||
return comment
|
||||
})
|
||||
|
||||
func proccess(_ feed: inout CommentFetchResult, topicHandle: String) {
|
||||
commands = fetchAllCommentCommands()
|
||||
applyCreateDeleteCommands(to: &feed, topicHandle: topicHandle)
|
||||
applyCommentActionCommands(to: &feed)
|
||||
applyReplyCommands(to: &feed)
|
||||
}
|
||||
|
||||
private func fetchCommentActionCommands() -> [CommentCommand] {
|
||||
let request = CacheFetchRequest(resultType: OutgoingCommand.self,
|
||||
predicate: PredicateBuilder().commentActionCommands(),
|
||||
sortDescriptors: [Cache.createdAtSortDescriptor])
|
||||
private func fetchAllCommentCommands() -> [CommentCommand] {
|
||||
let request = CacheFetchRequest(
|
||||
resultType: OutgoingCommand.self,
|
||||
predicate: predicateBuilder.allCommentCommands(),
|
||||
sortDescriptors: [Cache.createdAtSortDescriptor]
|
||||
)
|
||||
|
||||
return cache.fetchOutgoing(with: request) as? [CommentCommand] ?? []
|
||||
}
|
||||
|
||||
private func fetchCreateDeleteCommentCommands() -> [CommentCommand] {
|
||||
let request = CacheFetchRequest(resultType: OutgoingCommand.self,
|
||||
predicate: PredicateBuilder().createDeleteCommentCommands(),
|
||||
sortDescriptors: [Cache.createdAtSortDescriptor])
|
||||
|
||||
return cache.fetchOutgoing(with: request) as? [CommentCommand] ?? []
|
||||
private func applyCreateDeleteCommands(to feed: inout CommentFetchResult, topicHandle: String) {
|
||||
for command in commands where !command.isActionCommand && command.comment.topicHandle == topicHandle {
|
||||
command.apply(to: &feed)
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchCreateDeleteReplyCommands() -> [ReplyCommand] {
|
||||
private func applyCommentActionCommands(to feed: inout CommentFetchResult) {
|
||||
for command in commands where command.isActionCommand {
|
||||
command.apply(to: &feed)
|
||||
}
|
||||
}
|
||||
|
||||
private func applyReplyCommands(to feed: inout CommentFetchResult) {
|
||||
for case let command in fetchCreateReplyCommands() {
|
||||
command.apply(to: &feed)
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchCreateReplyCommands() -> [CreateReplyCommand] {
|
||||
let request = CacheFetchRequest(resultType: OutgoingCommand.self,
|
||||
predicate: PredicateBuilder().createDeleteReplyCommands(),
|
||||
predicate: PredicateBuilder().createReplyCommands(),
|
||||
sortDescriptors: [Cache.createdAtSortDescriptor])
|
||||
|
||||
return cache.fetchOutgoing(with: request) as? [ReplyCommand] ?? []
|
||||
return cache.fetchOutgoing(with: request) as? [CreateReplyCommand] ?? []
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,21 +10,6 @@ typealias CommentFetchResultHandler = ((CommentFetchResult) -> Void)
|
|||
typealias CommentHandler = ((Comment) -> Void)
|
||||
typealias CommentPostResultHandler = ((Comment) -> Void)
|
||||
|
||||
enum CommentsServiceError: Error {
|
||||
case failedToFetch(message: String)
|
||||
case failedToLike(message: String)
|
||||
case failedToUnLike(message: String)
|
||||
|
||||
var message: String {
|
||||
switch self {
|
||||
case .failedToFetch(let message),
|
||||
.failedToLike(let message),
|
||||
.failedToUnLike(let message):
|
||||
return message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol CommentServiceProtocol {
|
||||
func fetchComments(topicHandle: String, cursor: String?, limit: Int32?, cachedResult: @escaping CommentFetchResultHandler, resultHandler: @escaping CommentFetchResultHandler)
|
||||
func comment(commentHandle: String, cachedResult: @escaping CommentHandler, success: @escaping CommentHandler, failure: @escaping Failure)
|
||||
|
@ -40,7 +25,7 @@ class CommentsService: BaseService, CommentServiceProtocol {
|
|||
private let predicateBuilder = PredicateBuilder()
|
||||
private let changesPublisher: Publisher
|
||||
|
||||
init(imagesService: ImagesServiceType, changesPublisher: Publisher = HandleChangesManager.shared) {
|
||||
init(imagesService: ImagesServiceType, changesPublisher: Publisher = HandleChangesMulticast.shared) {
|
||||
self.changesPublisher = changesPublisher
|
||||
self.imagesService = imagesService
|
||||
super.init()
|
||||
|
@ -71,7 +56,7 @@ class CommentsService: BaseService, CommentServiceProtocol {
|
|||
|
||||
if let commentView = result?.body {
|
||||
strongSelf.cache.cacheIncoming(commentView, for: builder.URLString)
|
||||
success(strongSelf.convert(commentView: commentView))
|
||||
success(Comment(commentView: commentView))
|
||||
} else if strongSelf.errorHandler.canHandle(error) {
|
||||
strongSelf.errorHandler.handle(error)
|
||||
failure(APIError(error: error))
|
||||
|
@ -99,7 +84,7 @@ class CommentsService: BaseService, CommentServiceProtocol {
|
|||
private func cachedIncomingComment(key: String) -> Comment? {
|
||||
let p = predicateBuilder.predicate(typeID: key)
|
||||
let cachedCommentView = cache.firstIncoming(ofType: CommentView.self, predicate: p, sortDescriptors: nil)
|
||||
return cachedCommentView != nil ? convert(commentView: cachedCommentView!) : nil
|
||||
return cachedCommentView != nil ? Comment(commentView: cachedCommentView!) : nil
|
||||
}
|
||||
|
||||
func fetchComments(topicHandle: String,
|
||||
|
@ -107,67 +92,65 @@ class CommentsService: BaseService, CommentServiceProtocol {
|
|||
limit: Int32? = nil,
|
||||
cachedResult: @escaping CommentFetchResultHandler,
|
||||
resultHandler: @escaping CommentFetchResultHandler) {
|
||||
|
||||
|
||||
let builder = CommentsAPI.topicCommentsGetTopicCommentsWithRequestBuilder(
|
||||
topicHandle: topicHandle,
|
||||
authorization: authorization,
|
||||
cursor: cursor, limit: limit
|
||||
cursor: cursor,
|
||||
limit: limit
|
||||
)
|
||||
|
||||
var result = CommentFetchResult()
|
||||
fetchCachedFeed(topicHandle: topicHandle, url: builder.URLString, cachedResult: cachedResult)
|
||||
|
||||
let fetchOutgoingRequest = CacheFetchRequest(resultType: OutgoingCommand.self,
|
||||
predicate: predicateBuilder.allCreateCommentCommands(for: topicHandle),
|
||||
sortDescriptors: [Cache.createdAtSortDescriptor])
|
||||
builder.execute { response, error in
|
||||
let feed = self.onCommentsFetched(topicHandle: topicHandle,
|
||||
cursor: cursor,
|
||||
url: builder.URLString,
|
||||
response: response?.body,
|
||||
error: error)
|
||||
resultHandler(feed)
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchCachedFeed(topicHandle: String, url: String, cachedResult: @escaping CommentFetchResultHandler) {
|
||||
let incomingFeed = cache.firstIncoming(ofType: FeedResponseCommentView.self,
|
||||
predicate: predicateBuilder.predicate(handle: url),
|
||||
sortDescriptors: nil)
|
||||
|
||||
let commands = cache.fetchOutgoing(with: fetchOutgoingRequest)
|
||||
let incomingFeed = self.cache.firstIncoming(ofType: FeedResponseCommentView.self,
|
||||
predicate: predicateBuilder.predicate(handle: builder.URLString),
|
||||
sortDescriptors: nil)
|
||||
var feed = CommentFetchResult()
|
||||
feed.comments = incomingFeed?.data?.map(Comment.init) ?? []
|
||||
processor.proccess(&feed, topicHandle: topicHandle)
|
||||
cachedResult(feed)
|
||||
}
|
||||
|
||||
func onCommentsFetched(topicHandle: String,
|
||||
cursor: String?,
|
||||
url: String,
|
||||
response: FeedResponseCommentView?,
|
||||
error: ErrorResponse?) -> CommentFetchResult {
|
||||
|
||||
let incomingComments = incomingFeed?.data?.map(self.convert(commentView:)) ?? []
|
||||
let typeID = "fetch_commens-\(topicHandle)"
|
||||
|
||||
let outgoingComments = commands.flatMap { ($0 as? CreateCommentCommand)?.comment }
|
||||
|
||||
result.comments = outgoingComments + incomingComments
|
||||
result.cursor = incomingFeed?.cursor
|
||||
|
||||
processor.proccess(&result)
|
||||
cachedResult(result)
|
||||
|
||||
guard isNetworkReachable else {
|
||||
result.error = CommentsServiceError.failedToFetch(message: L10n.Error.unknown)
|
||||
resultHandler(result)
|
||||
return
|
||||
if cursor == nil {
|
||||
cache.deleteIncoming(with: predicateBuilder.predicate(typeID: typeID))
|
||||
}
|
||||
|
||||
builder.execute { [weak self, predicateBuilder] (response, error) in
|
||||
|
||||
result = CommentFetchResult()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let typeID = "fetch_commens-\(topicHandle)"
|
||||
|
||||
if cursor == nil {
|
||||
strongSelf.cache.deleteIncoming(with: predicateBuilder.predicate(typeID: typeID))
|
||||
}
|
||||
|
||||
if let body = response?.body, let data = body.data {
|
||||
body.handle = builder.URLString
|
||||
strongSelf.cache.cacheIncoming(body, for: typeID)
|
||||
result.comments = strongSelf.convert(data: data)
|
||||
result.cursor = body.cursor
|
||||
} else if strongSelf.errorHandler.canHandle(error) {
|
||||
strongSelf.errorHandler.handle(error)
|
||||
} else if let error = error {
|
||||
result.error = CommentsServiceError.failedToFetch(message: error.localizedDescription)
|
||||
}
|
||||
|
||||
resultHandler(result)
|
||||
var feed = CommentFetchResult()
|
||||
|
||||
if let response = response, let data = response.data {
|
||||
response.handle = url
|
||||
cache.cacheIncoming(response, for: typeID)
|
||||
feed.comments = data.map(Comment.init)
|
||||
feed.cursor = response.cursor
|
||||
} else if errorHandler.canHandle(error) {
|
||||
errorHandler.handle(error)
|
||||
} else if let error = error {
|
||||
feed.error = APIError(error: error)
|
||||
}
|
||||
|
||||
processor.proccess(&feed, topicHandle: topicHandle)
|
||||
|
||||
return feed
|
||||
}
|
||||
|
||||
func postComment(comment: Comment,
|
||||
|
@ -195,12 +178,15 @@ class CommentsService: BaseService, CommentServiceProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
private func execute(command: CreateCommentCommand,
|
||||
resultHandler: @escaping CommentPostResultHandler,
|
||||
failure: @escaping Failure) {
|
||||
func execute(command: CreateCommentCommand,
|
||||
resultHandler: @escaping CommentPostResultHandler,
|
||||
failure: @escaping Failure) {
|
||||
|
||||
cache.cacheOutgoing(command)
|
||||
resultHandler(command.comment)
|
||||
let isCached = cache.isCached(command)
|
||||
if !isCached {
|
||||
cache.cacheOutgoing(command)
|
||||
resultHandler(command.comment)
|
||||
}
|
||||
|
||||
CommentsAPI.topicCommentsPostComment(
|
||||
topicHandle: command.comment.topicHandle,
|
||||
|
@ -208,9 +194,17 @@ class CommentsService: BaseService, CommentServiceProtocol {
|
|||
authorization: authorization
|
||||
) { response, error in
|
||||
if let newHandle = response?.commentHandle {
|
||||
self.onCommentPosted(oldCommand: command, newHandle: newHandle)
|
||||
if !isCached {
|
||||
self.onCommentPosted(oldCommand: command, newHandle: newHandle)
|
||||
} else {
|
||||
command.comment.commentHandle = newHandle
|
||||
resultHandler(command.comment)
|
||||
}
|
||||
} else if self.errorHandler.canHandle(error) {
|
||||
self.errorHandler.handle(error)
|
||||
failure(error ?? APIError.unknown)
|
||||
} else if let error = error {
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -225,9 +219,14 @@ class CommentsService: BaseService, CommentServiceProtocol {
|
|||
}
|
||||
|
||||
func delete(comment: Comment, completion: @escaping (Result<Void>) -> Void) {
|
||||
completion(.success())
|
||||
|
||||
let command = RemoveCommentCommand(comment: comment)
|
||||
|
||||
let isCached = cache.isCached(command)
|
||||
|
||||
if !isCached {
|
||||
completion(.success())
|
||||
}
|
||||
|
||||
let hasDeletedInverseCommand = cache.deleteInverseCommand(for: command)
|
||||
|
||||
guard !hasDeletedInverseCommand else {
|
||||
|
@ -243,24 +242,23 @@ class CommentsService: BaseService, CommentServiceProtocol {
|
|||
self.errorHandler.handle(error)
|
||||
completion(.failure(APIError(error: error)))
|
||||
} else if error == nil {
|
||||
let p = self.predicateBuilder.predicate(for: command)
|
||||
self.cache.deleteOutgoing(with: p)
|
||||
if !isCached {
|
||||
self.onCommentDeleted(command: command)
|
||||
} else {
|
||||
completion(.success())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func convert(data: [CommentView]) -> [Comment] {
|
||||
return data.map(Comment.init(commentView:))
|
||||
}
|
||||
|
||||
private func convert(commentView: CommentView) -> Comment {
|
||||
return Comment(commentView: commentView)
|
||||
private func onCommentDeleted(command: RemoveCommentCommand) {
|
||||
cache.deleteOutgoing(with: predicateBuilder.predicate(for: command))
|
||||
}
|
||||
}
|
||||
|
||||
struct CommentFetchResult {
|
||||
var comments = [Comment]()
|
||||
var error: CommentsServiceError?
|
||||
var error: Error?
|
||||
var cursor: String?
|
||||
}
|
||||
|
||||
|
|
|
@ -44,25 +44,35 @@ class ImagesService: BaseService, ImagesServiceType {
|
|||
imageType: ImagesAPI.ImageType_imagesPostImage,
|
||||
completion: @escaping (Result<String>) -> Void) {
|
||||
|
||||
cacheCommand(command)
|
||||
completion(.success(command.photo.uid))
|
||||
cacheCommandAndComplete(command, completion: completion)
|
||||
|
||||
uploadImageData(command.photo.image?.compressed(), imageType: imageType) { result in
|
||||
if let handle = result.value {
|
||||
let photo = Photo(uid: handle, image: command.photo.image)
|
||||
self.imageCache.store(photo: photo)
|
||||
self.deleteCommand(command)
|
||||
} else if self.errorHandler.canHandle(result.error) {
|
||||
self.errorHandler.handle(result.error)
|
||||
}
|
||||
uploadImageData(command.photo.image?.compressed(), imageType: imageType) { [weak self] result in
|
||||
self?.onImageDataUploaded(command: command, result: result, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func cacheCommand(_ command: ImageCommand) {
|
||||
func cacheCommandAndComplete(_ command: ImageCommand, completion: @escaping (Result<String>) -> Void) {
|
||||
cache.cacheOutgoing(command)
|
||||
imageCache.store(photo: command.photo)
|
||||
}
|
||||
|
||||
private func onImageDataUploaded(command: ImageCommand, result: Result<String>, completion: @escaping (Result<String>) -> Void) {
|
||||
if let handle = result.value {
|
||||
let photo = Photo(uid: handle, image: command.photo.image)
|
||||
self.imageCache.store(photo: photo)
|
||||
self.deleteCommand(command)
|
||||
onImageDataUploadSuccess(newHandle: handle, completion: completion)
|
||||
} else if self.errorHandler.canHandle(result.error) {
|
||||
self.errorHandler.handle(result.error)
|
||||
} else {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
|
||||
func onImageDataUploadSuccess(newHandle: String, completion: @escaping (Result<String>) -> Void) {
|
||||
|
||||
}
|
||||
|
||||
private func deleteCommand(_ command: ImageCommand) {
|
||||
cache.deleteOutgoing(with: PredicateBuilder().predicate(for: command))
|
||||
imageCache.remove(photo: command.photo)
|
||||
|
@ -100,9 +110,9 @@ class ImagesService: BaseService, ImagesServiceType {
|
|||
}
|
||||
}
|
||||
|
||||
private func uploadImageData(_ data: Data?,
|
||||
imageType: ImagesAPI.ImageType_imagesPostImage,
|
||||
completion: @escaping (Result<String>) -> Void) {
|
||||
func uploadImageData(_ data: Data?,
|
||||
imageType: ImagesAPI.ImageType_imagesPostImage,
|
||||
completion: @escaping (Result<String>) -> Void) {
|
||||
uploadImageData(data, imageType: imageType, authorization: authorization, completion: completion)
|
||||
}
|
||||
|
||||
|
|
|
@ -56,11 +56,12 @@ class LikesService: BaseService, LikesServiceProtocol {
|
|||
private var requestExecutor: UsersFeedRequestExecutor!
|
||||
private var outgoingActionsExecutor: AtomicOutgoingCommandsExecutor!
|
||||
|
||||
init(ExecutorProvider provider: CacheRequestExecutorProviderType.Type = CacheRequestExecutorProvider.self) {
|
||||
init(executorProvider provider: CacheRequestExecutorProviderType.Type = CacheRequestExecutorProvider.self) {
|
||||
super.init()
|
||||
requestExecutor = provider.makeUsersFeedExecutor(for: self)
|
||||
outgoingActionsExecutor = provider.makeAtomicOutgoingCommandsExecutor(for: self)
|
||||
}
|
||||
|
||||
func postLike(post: Post, completion: @escaping CompletionHandler) {
|
||||
let builder = LikesAPI.topicLikesPostLikeWithRequestBuilder(topicHandle: post.topicHandle, authorization: authorization)
|
||||
let command = LikeTopicCommand(topic: post)
|
||||
|
|
|
@ -7,47 +7,45 @@
|
|||
import Foundation
|
||||
|
||||
protocol RepliesProcessorType {
|
||||
func proccess(_ fetchResult: inout RepliesFetchResult)
|
||||
func proccess(_ fetchResult: inout RepliesFetchResult, commentHandle: String)
|
||||
}
|
||||
|
||||
class RepliesProcessor: RepliesProcessorType {
|
||||
|
||||
let cache: CacheType
|
||||
private let cache: CacheType
|
||||
private let predicateBuilder = PredicateBuilder()
|
||||
private var commands: [ReplyCommand] = []
|
||||
|
||||
required init(cache: CacheType) {
|
||||
init(cache: CacheType) {
|
||||
self.cache = cache
|
||||
}
|
||||
|
||||
func proccess(_ fetchResult: inout RepliesFetchResult) {
|
||||
let commands = fetchCreateDeleteReplyCommands() + fetchReplyActionCommands()
|
||||
fetchCreateDeleteReplyCommands().filter( { $0.self is RemoveReplyCommand } ) .forEach { (commands) in
|
||||
fetchResult.replies = fetchResult.replies.filter({ $0.replyHandle != commands.reply.replyHandle })
|
||||
func proccess(_ feed: inout RepliesFetchResult, commentHandle: String) {
|
||||
commands = fetchAllReplyCommands()
|
||||
applyCreateDeleteCommands(to: &feed, commentHandle: commentHandle)
|
||||
applyReplyActionCommands(to: &feed)
|
||||
}
|
||||
|
||||
private func fetchAllReplyCommands() -> [ReplyCommand] {
|
||||
let request = CacheFetchRequest(
|
||||
resultType: OutgoingCommand.self,
|
||||
predicate: predicateBuilder.allReplyCommands(),
|
||||
sortDescriptors: [Cache.createdAtSortDescriptor]
|
||||
)
|
||||
|
||||
return cache.fetchOutgoing(with: request) as? [ReplyCommand] ?? []
|
||||
}
|
||||
|
||||
private func applyCreateDeleteCommands(to feed: inout RepliesFetchResult, commentHandle: String) {
|
||||
for command in commands where !command.isActionCommand && command.reply.commentHandle == commentHandle {
|
||||
command.apply(to: &feed)
|
||||
}
|
||||
|
||||
fetchResult.replies = fetchResult.replies.map({ (reply) -> Reply in
|
||||
var reply = reply
|
||||
let commandsToApply = commands.filter { $0.reply.replyHandle == reply.commentHandle }
|
||||
for command in commandsToApply {
|
||||
command.apply(to: &reply)
|
||||
}
|
||||
return reply
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private func fetchReplyActionCommands() -> [ReplyCommand] {
|
||||
let request = CacheFetchRequest(resultType: OutgoingCommand.self,
|
||||
predicate: PredicateBuilder().replyActionCommands(),
|
||||
sortDescriptors: [Cache.createdAtSortDescriptor])
|
||||
|
||||
return cache.fetchOutgoing(with: request) as? [ReplyCommand] ?? []
|
||||
}
|
||||
|
||||
private func fetchCreateDeleteReplyCommands() -> [ReplyCommand] {
|
||||
let request = CacheFetchRequest(resultType: OutgoingCommand.self,
|
||||
predicate: PredicateBuilder().createDeleteReplyCommands(),
|
||||
sortDescriptors: [Cache.createdAtSortDescriptor])
|
||||
|
||||
return cache.fetchOutgoing(with: request) as? [ReplyCommand] ?? []
|
||||
private func applyReplyActionCommands(to feed: inout RepliesFetchResult) {
|
||||
for command in commands where command.isActionCommand {
|
||||
command.apply(to: &feed)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class RepliesService: BaseService, RepliesServiceProtcol {
|
|||
private var processor: RepliesProcessorType!
|
||||
private let changesPublisher: Publisher
|
||||
|
||||
init(changesPublisher: Publisher = HandleChangesManager.shared) {
|
||||
init(changesPublisher: Publisher = HandleChangesMulticast.shared) {
|
||||
self.changesPublisher = changesPublisher
|
||||
super.init()
|
||||
processor = RepliesProcessor(cache: cache)
|
||||
|
@ -47,70 +47,59 @@ class RepliesService: BaseService, RepliesServiceProtcol {
|
|||
limit: Int32(limit)
|
||||
)
|
||||
|
||||
var result = RepliesFetchResult()
|
||||
|
||||
let fetchOutgoingRequest = CacheFetchRequest(resultType: OutgoingCommand.self,
|
||||
predicate: PredicateBuilder().allCreateReplyCommands(),
|
||||
sortDescriptors: [Cache.createdAtSortDescriptor])
|
||||
|
||||
let commands = cache.fetchOutgoing(with: fetchOutgoingRequest)
|
||||
let outgoingReplies = commands.flatMap { ($0 as? CreateReplyCommand)?.reply }
|
||||
let incomingFeed = self.cache.firstIncoming(ofType: FeedResponseReplyView.self,
|
||||
predicate: PredicateBuilder().predicate(handle: builder.URLString),
|
||||
sortDescriptors: nil)
|
||||
|
||||
let incomingReplies = incomingFeed?.data?.map(self.convert(replyView:)) ?? []
|
||||
|
||||
|
||||
result.replies = outgoingReplies + incomingReplies
|
||||
result.cursor = incomingFeed?.cursor
|
||||
|
||||
processor.proccess(&result)
|
||||
cachedResult(result)
|
||||
|
||||
|
||||
guard isNetworkReachable else {
|
||||
result.error = APIError.unknown
|
||||
resultHandler(result)
|
||||
return
|
||||
}
|
||||
|
||||
builder.execute { [weak self] (response, error) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let typeID = "fetch_replies-\(commentHandle)"
|
||||
|
||||
if cursor == nil {
|
||||
strongSelf.cache.deleteIncoming(with: PredicateBuilder().predicate(typeID: typeID))
|
||||
}
|
||||
fetchCachedFeed(commentHandle: commentHandle, url: builder.URLString, cachedResult: cachedResult)
|
||||
|
||||
if let body = response?.body, let data = body.data {
|
||||
body.handle = builder.URLString
|
||||
strongSelf.cache.cacheIncoming(body, for: typeID)
|
||||
result.replies = strongSelf.convert(data: data)
|
||||
result.cursor = body.cursor
|
||||
} else if strongSelf.errorHandler.canHandle(error) {
|
||||
strongSelf.errorHandler.handle(error)
|
||||
return
|
||||
} else {
|
||||
guard let unwrappedError = error else {
|
||||
resultHandler(result)
|
||||
return
|
||||
}
|
||||
|
||||
if unwrappedError.statusCode >= Constants.HTTPStatusCodes.InternalServerError.rawValue {
|
||||
resultHandler(result)
|
||||
} else {
|
||||
result.error = APIError(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
resultHandler(result)
|
||||
builder.execute { response, error in
|
||||
let feed = self.onRepliesFetched(commentHandle: commentHandle,
|
||||
cursor: cursor,
|
||||
url: builder.URLString,
|
||||
response: response?.body,
|
||||
error: error)
|
||||
resultHandler(feed)
|
||||
}
|
||||
}
|
||||
|
||||
func onRepliesFetched(commentHandle: String,
|
||||
cursor: String?,
|
||||
url: String,
|
||||
response: FeedResponseReplyView?,
|
||||
error: ErrorResponse?) -> RepliesFetchResult {
|
||||
|
||||
let typeID = "fetch_replies-\(commentHandle)"
|
||||
|
||||
if cursor == nil {
|
||||
cache.deleteIncoming(with: PredicateBuilder().predicate(typeID: typeID))
|
||||
}
|
||||
|
||||
var feed = RepliesFetchResult()
|
||||
|
||||
if let response = response, let data = response.data {
|
||||
response.handle = url
|
||||
cache.cacheIncoming(response, for: typeID)
|
||||
feed.replies = data.map(Reply.init)
|
||||
feed.cursor = response.cursor
|
||||
} else if errorHandler.canHandle(error) {
|
||||
errorHandler.handle(error)
|
||||
} else if let error = error {
|
||||
feed.error = APIError(error: error)
|
||||
}
|
||||
|
||||
processor.proccess(&feed, commentHandle: commentHandle)
|
||||
|
||||
return feed
|
||||
}
|
||||
|
||||
private func fetchCachedFeed(commentHandle: String, url: String, cachedResult: @escaping RepliesFetchResultHandler) {
|
||||
let incomingFeed = cache.firstIncoming(ofType: FeedResponseReplyView.self,
|
||||
predicate: PredicateBuilder().predicate(handle: url),
|
||||
sortDescriptors: nil)
|
||||
|
||||
var feed = RepliesFetchResult()
|
||||
feed.replies = incomingFeed?.data?.map(Reply.init) ?? []
|
||||
processor.proccess(&feed, commentHandle: commentHandle)
|
||||
cachedResult(feed)
|
||||
}
|
||||
|
||||
func reply(replyHandle: String,
|
||||
cachedResult: @escaping ReplyHandler,
|
||||
success: @escaping ReplyHandler,
|
||||
|
@ -166,9 +155,13 @@ class RepliesService: BaseService, RepliesServiceProtcol {
|
|||
}
|
||||
|
||||
func postReply(reply: Reply, success: @escaping PostReplyResultHandler, failure: @escaping (APIError) -> (Void)) {
|
||||
let replyCommand = CreateReplyCommand(reply: reply)
|
||||
cache.cacheOutgoing(replyCommand)
|
||||
success(PostReplyResponse(reply: reply))
|
||||
let command = CreateReplyCommand(reply: reply)
|
||||
|
||||
let isCached = cache.isCached(command)
|
||||
if !isCached {
|
||||
cache.cacheOutgoing(command)
|
||||
success(PostReplyResponse(reply: reply))
|
||||
}
|
||||
|
||||
let request = PostReplyRequest()
|
||||
request.text = reply.text
|
||||
|
@ -181,8 +174,13 @@ class RepliesService: BaseService, RepliesServiceProtcol {
|
|||
if self.errorHandler.canHandle(error) {
|
||||
self.errorHandler.handle(error)
|
||||
failure(APIError(error: error))
|
||||
} else if let response = response {
|
||||
self.onReplyPosted(oldCommand: replyCommand, response: response)
|
||||
} else if let response = response, let newHandle = response.replyHandle {
|
||||
if !isCached {
|
||||
self.onReplyPosted(oldCommand: command, response: response)
|
||||
} else {
|
||||
command.reply.replyHandle = newHandle
|
||||
success(PostReplyResponse(reply: reply))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -197,9 +195,15 @@ class RepliesService: BaseService, RepliesServiceProtcol {
|
|||
changesPublisher.notify(ReplyUpdateHint(oldHandle: oldHandle, newHandle: newHandle))
|
||||
}
|
||||
|
||||
func delete(reply: Reply, completion: @escaping ((Result<Void>) -> Void)) {
|
||||
func delete(reply: Reply, completion: @escaping (Result<Void>) -> Void) {
|
||||
let command = RemoveReplyCommand(reply: reply)
|
||||
|
||||
let isCached = cache.isCached(command)
|
||||
|
||||
if !isCached {
|
||||
completion(.success())
|
||||
}
|
||||
|
||||
let hasDeletedInverseCommand = cache.deleteInverseCommand(for: command)
|
||||
|
||||
guard !hasDeletedInverseCommand else {
|
||||
|
@ -215,13 +219,19 @@ class RepliesService: BaseService, RepliesServiceProtcol {
|
|||
self.errorHandler.handle(error)
|
||||
completion(.failure(APIError(error: error)))
|
||||
} else if error == nil {
|
||||
let p = PredicateBuilder().predicate(for: command)
|
||||
self.cache.deleteOutgoing(with: p)
|
||||
completion(.success())
|
||||
if !isCached {
|
||||
self.onReplyDeleted(command: command)
|
||||
} else {
|
||||
completion(.success())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func onReplyDeleted(command: RemoveReplyCommand) {
|
||||
self.cache.deleteOutgoing(with: PredicateBuilder().predicate(for: command))
|
||||
}
|
||||
|
||||
private func convert(data: [ReplyView]) -> [Reply] {
|
||||
return data.map(Reply.init(replyView:))
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class SocialService: BaseService, SocialServiceType {
|
|||
|
||||
fileprivate let executorProvider: CacheRequestExecutorProviderType.Type
|
||||
|
||||
init(ExecutorProvider provider: CacheRequestExecutorProviderType.Type = CacheRequestExecutorProvider.self) {
|
||||
init(executorProvider provider: CacheRequestExecutorProviderType.Type = CacheRequestExecutorProvider.self) {
|
||||
self.executorProvider = provider
|
||||
super.init()
|
||||
usersFeedExecutor = provider.makeUsersFeedExecutor(for: self)
|
||||
|
|
|
@ -96,7 +96,7 @@ class TopicService: BaseService, PostServiceProtocol {
|
|||
init(imagesService: ImagesServiceType,
|
||||
ExecutorProvider: CacheRequestExecutorProviderType.Type = CacheRequestExecutorProvider.self,
|
||||
predicateBuilder: PredicateBuilder = PredicateBuilder(),
|
||||
changesPublisher: Publisher = HandleChangesManager.shared) {
|
||||
changesPublisher: Publisher = HandleChangesMulticast.shared) {
|
||||
|
||||
self.imagesService = imagesService
|
||||
self.executorProvider = ExecutorProvider
|
||||
|
|
|
@ -6,31 +6,15 @@
|
|||
import Foundation
|
||||
|
||||
final class DaemonsController: Daemon {
|
||||
private let networkTracker: NetworkStatusMulticast
|
||||
private let cache: CacheType
|
||||
|
||||
private lazy var outgoingCacheDaemon: OutgoingCommandsUploader = { [unowned self] in
|
||||
let queue = OperationQueue()
|
||||
queue.name = "OutgoingCommandsUploader-executionQueue"
|
||||
queue.qualityOfService = .background
|
||||
queue.maxConcurrentOperationCount = 1
|
||||
|
||||
let strategy = OutgoingCommandsUploadStrategy(cache: self.cache,
|
||||
operationsBuilderType: OutgoingCommandOperationsBuilder(),
|
||||
executionQueue: queue)
|
||||
|
||||
return OutgoingCommandsUploader(networkTracker: self.networkTracker,
|
||||
uploadStrategy: strategy,
|
||||
jsonDecoderType: Decoders.self)
|
||||
}()
|
||||
|
||||
private lazy var daemons: [Daemon] = { [unowned self] in
|
||||
return [self.outgoingCacheDaemon]
|
||||
return [self.factory.makeOutgoingCacheDaemon(), self.factory.makeHandleUpdaterDaemon()]
|
||||
}()
|
||||
|
||||
init(networkTracker: NetworkStatusMulticast, cache: CacheType) {
|
||||
self.networkTracker = networkTracker
|
||||
self.cache = cache
|
||||
private let factory: DaemonsFactory
|
||||
|
||||
init(factory: DaemonsFactory) {
|
||||
self.factory = factory
|
||||
}
|
||||
|
||||
func start() {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol DaemonsFactory {
|
||||
func makeOutgoingCacheDaemon() -> Daemon
|
||||
func makeHandleUpdaterDaemon() -> Daemon
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class DaemonsFactoryImpl: DaemonsFactory {
|
||||
|
||||
private let cache: CacheType
|
||||
private let networkTracker: NetworkTrackerType
|
||||
|
||||
init(cache: CacheType, networkTracker: NetworkTrackerType) {
|
||||
self.cache = cache
|
||||
self.networkTracker = networkTracker
|
||||
}
|
||||
|
||||
func makeOutgoingCacheDaemon() -> Daemon {
|
||||
let queue = OperationQueue()
|
||||
queue.name = "OutgoingCommandsUploader-executionQueue"
|
||||
queue.qualityOfService = .background
|
||||
queue.maxConcurrentOperationCount = 1
|
||||
|
||||
let strategy = OutgoingCommandsUploadStrategy(cache: self.cache,
|
||||
operationsBuilderType: OutgoingCommandOperationsBuilder(),
|
||||
executionQueue: queue)
|
||||
|
||||
return OutgoingCommandsUploader(networkTracker: self.networkTracker,
|
||||
uploadStrategy: strategy,
|
||||
jsonDecoderType: Decoders.self)
|
||||
}
|
||||
|
||||
func makeHandleUpdaterDaemon() -> Daemon {
|
||||
return HandleUpdateDaemon()
|
||||
}
|
||||
}
|
|
@ -68,7 +68,8 @@ final class SocialPlusServices: SocialPlusServicesType {
|
|||
}
|
||||
|
||||
func getDaemonsController(cache: CacheType) -> Daemon {
|
||||
return DaemonsController(networkTracker: networkTracker, cache: cache)
|
||||
let factory = DaemonsFactoryImpl(cache: cache, networkTracker: networkTracker)
|
||||
return DaemonsController(factory: factory)
|
||||
}
|
||||
|
||||
func getStartupCommands(launchArgs: LaunchArguments) -> [Command] {
|
||||
|
|
|
@ -54,7 +54,7 @@ class CommentRepliesPresenter: CommentRepliesModuleInput, CommentRepliesViewOutp
|
|||
|
||||
init(pageSize: Int,
|
||||
actionStrategy: AuthorizedActionStrategy,
|
||||
handlePublisher: Publisher = HandleChangesManager.shared) {
|
||||
handlePublisher: Publisher = HandleChangesMulticast.shared) {
|
||||
self.pageSize = pageSize
|
||||
self.actionStrategy = actionStrategy
|
||||
handlePublisher.subscribe(self)
|
||||
|
|
|
@ -40,7 +40,7 @@ class FeedModuleInteractor: FeedModuleInteractorInput {
|
|||
var searchService: SearchServiceType!
|
||||
weak var userHolder: UserHolder? = SocialPlus.shared
|
||||
|
||||
init(handleChangesPublisher: Publisher = HandleChangesManager.shared) {
|
||||
init(handleChangesPublisher: Publisher = HandleChangesMulticast.shared) {
|
||||
handleChangesPublisher.subscribe(self)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import Foundation
|
|||
protocol PostDetailInteractorOutput: class {
|
||||
func didFetch(comments: [Comment], cursor: String?)
|
||||
func didFetchMore(comments: [Comment], cursor: String?)
|
||||
func didFail(error: CommentsServiceError)
|
||||
func didFail(error: Error)
|
||||
func commentDidPost(comment: Comment)
|
||||
func commentPostFailed(error: Error)
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class PostDetailPresenter: PostDetailViewOutput, PostDetailInteractorOutput, Pos
|
|||
|
||||
init(pageSize: Int,
|
||||
actionStrategy: AuthorizedActionStrategy,
|
||||
handleChangesPublisher: Publisher = HandleChangesManager.shared) {
|
||||
handleChangesPublisher: Publisher = HandleChangesMulticast.shared) {
|
||||
self.pageSize = pageSize
|
||||
self.actionStrategy = actionStrategy
|
||||
handleChangesPublisher.subscribe(self)
|
||||
|
@ -94,7 +94,7 @@ class PostDetailPresenter: PostDetailViewOutput, PostDetailInteractorOutput, Pos
|
|||
}
|
||||
}
|
||||
|
||||
func didFail(error: CommentsServiceError) {
|
||||
func didFail(error: Error) {
|
||||
loadMoreCellViewModel.cellHeight = 0.1
|
||||
loadMoreCellViewModel.stopLoading()
|
||||
view.updateLoadingCell()
|
||||
|
|
Загрузка…
Ссылка в новой задаче