mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-07 14:53:35 +00:00
no message
This commit is contained in:
parent
6f5513f01a
commit
59e1f10908
@ -38,6 +38,8 @@
|
|||||||
D021E0D61DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0D51DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift */; };
|
D021E0D61DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0D51DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift */; };
|
||||||
D021E0D81DB4FD1300C6B04F /* ItemCollectionItemTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0D71DB4FD1300C6B04F /* ItemCollectionItemTable.swift */; };
|
D021E0D81DB4FD1300C6B04F /* ItemCollectionItemTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0D71DB4FD1300C6B04F /* ItemCollectionItemTable.swift */; };
|
||||||
D021E0DC1DB5237C00C6B04F /* ItemCollectionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DB1DB5237C00C6B04F /* ItemCollectionsView.swift */; };
|
D021E0DC1DB5237C00C6B04F /* ItemCollectionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DB1DB5237C00C6B04F /* ItemCollectionsView.swift */; };
|
||||||
|
D021FC262024B83700C34AB7 /* FileSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021FC252024B83700C34AB7 /* FileSize.swift */; };
|
||||||
|
D021FC272024B83700C34AB7 /* FileSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021FC252024B83700C34AB7 /* FileSize.swift */; };
|
||||||
D02EB8071D2B07F300D07ED3 /* OrderStatisticTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02EB8061D2B07F300D07ED3 /* OrderStatisticTreeTests.swift */; };
|
D02EB8071D2B07F300D07ED3 /* OrderStatisticTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02EB8061D2B07F300D07ED3 /* OrderStatisticTreeTests.swift */; };
|
||||||
D03120F81DA53FF4006A2A60 /* PeerPresenceTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120F71DA53FF4006A2A60 /* PeerPresenceTable.swift */; };
|
D03120F81DA53FF4006A2A60 /* PeerPresenceTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120F71DA53FF4006A2A60 /* PeerPresenceTable.swift */; };
|
||||||
D03120FA1DA540F0006A2A60 /* CachedPeerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120F91DA540F0006A2A60 /* CachedPeerData.swift */; };
|
D03120FA1DA540F0006A2A60 /* CachedPeerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120F91DA540F0006A2A60 /* CachedPeerData.swift */; };
|
||||||
@ -343,6 +345,14 @@
|
|||||||
D0FA0ACB1E780A26005BB9B7 /* PostboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0AC91E780A26005BB9B7 /* PostboxView.swift */; };
|
D0FA0ACB1E780A26005BB9B7 /* PostboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0AC91E780A26005BB9B7 /* PostboxView.swift */; };
|
||||||
D0FA0ACD1E781067005BB9B7 /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ACC1E781067005BB9B7 /* Views.swift */; };
|
D0FA0ACD1E781067005BB9B7 /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ACC1E781067005BB9B7 /* Views.swift */; };
|
||||||
D0FA0ACE1E781067005BB9B7 /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ACC1E781067005BB9B7 /* Views.swift */; };
|
D0FA0ACE1E781067005BB9B7 /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ACC1E781067005BB9B7 /* Views.swift */; };
|
||||||
|
D0FC194A201E8EAF00FEDBB2 /* MediaBoxFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */; };
|
||||||
|
D0FC194B201E8EAF00FEDBB2 /* MediaBoxFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */; };
|
||||||
|
D0FC195020208E8800FEDBB2 /* Crc32.h in Headers */ = {isa = PBXBuildFile; fileRef = D0FC194E20208E8800FEDBB2 /* Crc32.h */; };
|
||||||
|
D0FC195120208E8800FEDBB2 /* Crc32.h in Headers */ = {isa = PBXBuildFile; fileRef = D0FC194E20208E8800FEDBB2 /* Crc32.h */; };
|
||||||
|
D0FC195220208E8800FEDBB2 /* Crc32.m in Sources */ = {isa = PBXBuildFile; fileRef = D0FC194F20208E8800FEDBB2 /* Crc32.m */; };
|
||||||
|
D0FC195320208E8800FEDBB2 /* Crc32.m in Sources */ = {isa = PBXBuildFile; fileRef = D0FC194F20208E8800FEDBB2 /* Crc32.m */; };
|
||||||
|
D0FC19552020CB7700FEDBB2 /* PeerGroupStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC19542020CB7700FEDBB2 /* PeerGroupStateView.swift */; };
|
||||||
|
D0FC19562020CB7700FEDBB2 /* PeerGroupStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC19542020CB7700FEDBB2 /* PeerGroupStateView.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -375,6 +385,7 @@
|
|||||||
D021E0D51DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionInfoTable.swift; sourceTree = "<group>"; };
|
D021E0D51DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionInfoTable.swift; sourceTree = "<group>"; };
|
||||||
D021E0D71DB4FD1300C6B04F /* ItemCollectionItemTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionItemTable.swift; sourceTree = "<group>"; };
|
D021E0D71DB4FD1300C6B04F /* ItemCollectionItemTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionItemTable.swift; sourceTree = "<group>"; };
|
||||||
D021E0DB1DB5237C00C6B04F /* ItemCollectionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionsView.swift; sourceTree = "<group>"; };
|
D021E0DB1DB5237C00C6B04F /* ItemCollectionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionsView.swift; sourceTree = "<group>"; };
|
||||||
|
D021FC252024B83700C34AB7 /* FileSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSize.swift; sourceTree = "<group>"; };
|
||||||
D02EB8061D2B07F300D07ED3 /* OrderStatisticTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatisticTreeTests.swift; sourceTree = "<group>"; };
|
D02EB8061D2B07F300D07ED3 /* OrderStatisticTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatisticTreeTests.swift; sourceTree = "<group>"; };
|
||||||
D03120F71DA53FF4006A2A60 /* PeerPresenceTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerPresenceTable.swift; sourceTree = "<group>"; };
|
D03120F71DA53FF4006A2A60 /* PeerPresenceTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerPresenceTable.swift; sourceTree = "<group>"; };
|
||||||
D03120F91DA540F0006A2A60 /* CachedPeerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPeerData.swift; sourceTree = "<group>"; };
|
D03120F91DA540F0006A2A60 /* CachedPeerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPeerData.swift; sourceTree = "<group>"; };
|
||||||
@ -538,6 +549,10 @@
|
|||||||
D0FA0AC61E77F0A2005BB9B7 /* ItemCollectionInfosView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionInfosView.swift; sourceTree = "<group>"; };
|
D0FA0AC61E77F0A2005BB9B7 /* ItemCollectionInfosView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionInfosView.swift; sourceTree = "<group>"; };
|
||||||
D0FA0AC91E780A26005BB9B7 /* PostboxView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxView.swift; sourceTree = "<group>"; };
|
D0FA0AC91E780A26005BB9B7 /* PostboxView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxView.swift; sourceTree = "<group>"; };
|
||||||
D0FA0ACC1E781067005BB9B7 /* Views.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Views.swift; sourceTree = "<group>"; };
|
D0FA0ACC1E781067005BB9B7 /* Views.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Views.swift; sourceTree = "<group>"; };
|
||||||
|
D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaBoxFile.swift; sourceTree = "<group>"; };
|
||||||
|
D0FC194E20208E8800FEDBB2 /* Crc32.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Crc32.h; sourceTree = "<group>"; };
|
||||||
|
D0FC194F20208E8800FEDBB2 /* Crc32.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Crc32.m; sourceTree = "<group>"; };
|
||||||
|
D0FC19542020CB7700FEDBB2 /* PeerGroupStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerGroupStateView.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -575,6 +590,7 @@
|
|||||||
D05F09A51C9E9F9300BB6F96 /* MediaResourceStatus.swift */,
|
D05F09A51C9E9F9300BB6F96 /* MediaResourceStatus.swift */,
|
||||||
D0CE63F51CA1CCB2002BC462 /* MediaResource.swift */,
|
D0CE63F51CA1CCB2002BC462 /* MediaResource.swift */,
|
||||||
D09ADF091D2E89F300C8208D /* RandomAccessMediaResourceContext.swift */,
|
D09ADF091D2E89F300C8208D /* RandomAccessMediaResourceContext.swift */,
|
||||||
|
D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */,
|
||||||
);
|
);
|
||||||
name = "Media Box";
|
name = "Media Box";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -754,6 +770,7 @@
|
|||||||
D0B1671F1F9EAAA900976B40 /* OrderedList.swift */,
|
D0B1671F1F9EAAA900976B40 /* OrderedList.swift */,
|
||||||
D0AA55121FB4C6AB00C2AB58 /* BinarySearch.swift */,
|
D0AA55121FB4C6AB00C2AB58 /* BinarySearch.swift */,
|
||||||
D0ECCB8E1FE9EB5500609802 /* PostboxLogging.swift */,
|
D0ECCB8E1FE9EB5500609802 /* PostboxLogging.swift */,
|
||||||
|
D021FC252024B83700C34AB7 /* FileSize.swift */,
|
||||||
);
|
);
|
||||||
name = Utils;
|
name = Utils;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -812,6 +829,7 @@
|
|||||||
D0C26D771FE31CA4004ABF18 /* GroupFeedReadStateSyncOperationsView.swift */,
|
D0C26D771FE31CA4004ABF18 /* GroupFeedReadStateSyncOperationsView.swift */,
|
||||||
D0C26D801FE41323004ABF18 /* ChatListGroupReferenceUnreadCounters.swift */,
|
D0C26D801FE41323004ABF18 /* ChatListGroupReferenceUnreadCounters.swift */,
|
||||||
D04614322004F2CC00EC0EF2 /* LocalMessageTagsView.swift */,
|
D04614322004F2CC00EC0EF2 /* LocalMessageTagsView.swift */,
|
||||||
|
D0FC19542020CB7700FEDBB2 /* PeerGroupStateView.swift */,
|
||||||
);
|
);
|
||||||
name = Views;
|
name = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -865,6 +883,8 @@
|
|||||||
D0E3A74D1B28A7E300A402D9 /* Supporting Files */ = {
|
D0E3A74D1B28A7E300A402D9 /* Supporting Files */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D0FC194E20208E8800FEDBB2 /* Crc32.h */,
|
||||||
|
D0FC194F20208E8800FEDBB2 /* Crc32.m */,
|
||||||
D044E1611B2AD667001EE087 /* MurMurHash32.h */,
|
D044E1611B2AD667001EE087 /* MurMurHash32.h */,
|
||||||
D044E1621B2AD677001EE087 /* MurMurHash32.m */,
|
D044E1621B2AD677001EE087 /* MurMurHash32.m */,
|
||||||
D0E3A74F1B28A7E300A402D9 /* Postbox.h */,
|
D0E3A74F1B28A7E300A402D9 /* Postbox.h */,
|
||||||
@ -909,6 +929,7 @@
|
|||||||
files = (
|
files = (
|
||||||
D0B418201D7DFDFD004562A4 /* sqlite3ext.h in Headers */,
|
D0B418201D7DFDFD004562A4 /* sqlite3ext.h in Headers */,
|
||||||
D0B4185D1D7DFE35004562A4 /* IpcNotifier.h in Headers */,
|
D0B4185D1D7DFE35004562A4 /* IpcNotifier.h in Headers */,
|
||||||
|
D0FC195120208E8800FEDBB2 /* Crc32.h in Headers */,
|
||||||
D050F2651E4A5B4800988324 /* fts3_tokenizer.h in Headers */,
|
D050F2651E4A5B4800988324 /* fts3_tokenizer.h in Headers */,
|
||||||
D0B4185B1D7DFE2C004562A4 /* MurMurHash32.h in Headers */,
|
D0B4185B1D7DFE2C004562A4 /* MurMurHash32.h in Headers */,
|
||||||
D0B4181E1D7DFDF8004562A4 /* SQLite-Bridging.h in Headers */,
|
D0B4181E1D7DFDF8004562A4 /* SQLite-Bridging.h in Headers */,
|
||||||
@ -923,6 +944,7 @@
|
|||||||
files = (
|
files = (
|
||||||
D07516771B2EC90400AE42E0 /* fts3_tokenizer.h in Headers */,
|
D07516771B2EC90400AE42E0 /* fts3_tokenizer.h in Headers */,
|
||||||
D07516451B2D9CEF00AE42E0 /* sqlite3.h in Headers */,
|
D07516451B2D9CEF00AE42E0 /* sqlite3.h in Headers */,
|
||||||
|
D0FC195020208E8800FEDBB2 /* Crc32.h in Headers */,
|
||||||
D0D511041D64D91C00A97B8A /* IpcNotifier.h in Headers */,
|
D0D511041D64D91C00A97B8A /* IpcNotifier.h in Headers */,
|
||||||
D07516781B2EC90400AE42E0 /* SQLite-Bridging.h in Headers */,
|
D07516781B2EC90400AE42E0 /* SQLite-Bridging.h in Headers */,
|
||||||
D0E3A7501B28A7E300A402D9 /* Postbox.h in Headers */,
|
D0E3A7501B28A7E300A402D9 /* Postbox.h in Headers */,
|
||||||
@ -1064,6 +1086,7 @@
|
|||||||
C20EB2A31F7179DC00DD3A57 /* PeerNotificationSettingsView.swift in Sources */,
|
C20EB2A31F7179DC00DD3A57 /* PeerNotificationSettingsView.swift in Sources */,
|
||||||
C25B56FE1F431C3300581D02 /* MessageHistoryTagsSummaryTable.swift in Sources */,
|
C25B56FE1F431C3300581D02 /* MessageHistoryTagsSummaryTable.swift in Sources */,
|
||||||
D050F2661E4A5B5A00988324 /* MessageGloballyUniqueIdTable.swift in Sources */,
|
D050F2661E4A5B5A00988324 /* MessageGloballyUniqueIdTable.swift in Sources */,
|
||||||
|
D0FC195320208E8800FEDBB2 /* Crc32.m in Sources */,
|
||||||
D03229EF1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */,
|
D03229EF1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */,
|
||||||
D0AA55141FB4C6AB00C2AB58 /* BinarySearch.swift in Sources */,
|
D0AA55141FB4C6AB00C2AB58 /* BinarySearch.swift in Sources */,
|
||||||
D01C7F081EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */,
|
D01C7F081EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */,
|
||||||
@ -1106,6 +1129,7 @@
|
|||||||
D0B418591D7DFE29004562A4 /* PostboxTransaction.swift in Sources */,
|
D0B418591D7DFE29004562A4 /* PostboxTransaction.swift in Sources */,
|
||||||
D0F7B1D21E045C6A007EB8A5 /* PeerNotificationSettingsTable.swift in Sources */,
|
D0F7B1D21E045C6A007EB8A5 /* PeerNotificationSettingsTable.swift in Sources */,
|
||||||
D0C26D701FE2E737004ABF18 /* GroupFeedReadState.swift in Sources */,
|
D0C26D701FE2E737004ABF18 /* GroupFeedReadState.swift in Sources */,
|
||||||
|
D0FC19562020CB7700FEDBB2 /* PeerGroupStateView.swift in Sources */,
|
||||||
D0E23DE31E808A9400B9B6D2 /* ItemCollectionIdsView.swift in Sources */,
|
D0E23DE31E808A9400B9B6D2 /* ItemCollectionIdsView.swift in Sources */,
|
||||||
D0B4181D1D7DFDF4004562A4 /* sqlite3.c in Sources */,
|
D0B4181D1D7DFDF4004562A4 /* sqlite3.c in Sources */,
|
||||||
D0F7B1C91E045C6A007EB8A5 /* MessageHistoryTagsTable.swift in Sources */,
|
D0F7B1C91E045C6A007EB8A5 /* MessageHistoryTagsTable.swift in Sources */,
|
||||||
@ -1135,9 +1159,11 @@
|
|||||||
D0FA0ACE1E781067005BB9B7 /* Views.swift in Sources */,
|
D0FA0ACE1E781067005BB9B7 /* Views.swift in Sources */,
|
||||||
D0B418501D7DFE20004562A4 /* UnsentMessageIndicesView.swift in Sources */,
|
D0B418501D7DFE20004562A4 /* UnsentMessageIndicesView.swift in Sources */,
|
||||||
D0BEAF641E54B2FA00BD963D /* AccountManager.swift in Sources */,
|
D0BEAF641E54B2FA00BD963D /* AccountManager.swift in Sources */,
|
||||||
|
D021FC272024B83700C34AB7 /* FileSize.swift in Sources */,
|
||||||
D073CE7F1DCBF3B4007511FD /* ItemCollection.swift in Sources */,
|
D073CE7F1DCBF3B4007511FD /* ItemCollection.swift in Sources */,
|
||||||
D07047B21F3DE40400F6A8D4 /* PendingMessageActionsSummaryView.swift in Sources */,
|
D07047B21F3DE40400F6A8D4 /* PendingMessageActionsSummaryView.swift in Sources */,
|
||||||
D0F7B1D91E045C6A007EB8A5 /* OrderStatisticTable.swift in Sources */,
|
D0F7B1D91E045C6A007EB8A5 /* OrderStatisticTable.swift in Sources */,
|
||||||
|
D0FC194B201E8EAF00FEDBB2 /* MediaBoxFile.swift in Sources */,
|
||||||
D073CEA01DCBF3C1007511FD /* ItemCollectionsView.swift in Sources */,
|
D073CEA01DCBF3C1007511FD /* ItemCollectionsView.swift in Sources */,
|
||||||
D0BE383A1E7C1FD4000079AF /* ItemCollectionInfoView.swift in Sources */,
|
D0BE383A1E7C1FD4000079AF /* ItemCollectionInfoView.swift in Sources */,
|
||||||
D0B418491D7DFE20004562A4 /* ChatListView.swift in Sources */,
|
D0B418491D7DFE20004562A4 /* ChatListView.swift in Sources */,
|
||||||
@ -1226,6 +1252,7 @@
|
|||||||
D0CE8CF61F703B1E00AA2DB0 /* PendingPeerNotificationSettingsIndexTable.swift in Sources */,
|
D0CE8CF61F703B1E00AA2DB0 /* PendingPeerNotificationSettingsIndexTable.swift in Sources */,
|
||||||
D03229EE1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */,
|
D03229EE1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */,
|
||||||
D01C7F071EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */,
|
D01C7F071EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */,
|
||||||
|
D021FC262024B83700C34AB7 /* FileSize.swift in Sources */,
|
||||||
D0AA55131FB4C6AB00C2AB58 /* BinarySearch.swift in Sources */,
|
D0AA55131FB4C6AB00C2AB58 /* BinarySearch.swift in Sources */,
|
||||||
D0F9E8631C579F0200037222 /* MediaCleanupTable.swift in Sources */,
|
D0F9E8631C579F0200037222 /* MediaCleanupTable.swift in Sources */,
|
||||||
D0943AF81FDAC53F001522CC /* ChatLocation.swift in Sources */,
|
D0943AF81FDAC53F001522CC /* ChatLocation.swift in Sources */,
|
||||||
@ -1335,10 +1362,12 @@
|
|||||||
D07827C11E0079CB00071108 /* StringIndexTokens.swift in Sources */,
|
D07827C11E0079CB00071108 /* StringIndexTokens.swift in Sources */,
|
||||||
D0F3CC721DDE1CDC008148FA /* ItemCacheTable.swift in Sources */,
|
D0F3CC721DDE1CDC008148FA /* ItemCacheTable.swift in Sources */,
|
||||||
D08C713C1C51283C00779C0F /* MessageHistoryIndexTable.swift in Sources */,
|
D08C713C1C51283C00779C0F /* MessageHistoryIndexTable.swift in Sources */,
|
||||||
|
D0FC195220208E8800FEDBB2 /* Crc32.m in Sources */,
|
||||||
D019B1CF1E2E770700F80DB3 /* MessageGloballyUniqueIdTable.swift in Sources */,
|
D019B1CF1E2E770700F80DB3 /* MessageGloballyUniqueIdTable.swift in Sources */,
|
||||||
D0F9E86D1C5A0E5D00037222 /* MetadataTable.swift in Sources */,
|
D0F9E86D1C5A0E5D00037222 /* MetadataTable.swift in Sources */,
|
||||||
D0DA44481E4C7D1E005FDCA7 /* PostboxAccess.swift in Sources */,
|
D0DA44481E4C7D1E005FDCA7 /* PostboxAccess.swift in Sources */,
|
||||||
D0B167201F9EAAA900976B40 /* OrderedList.swift in Sources */,
|
D0B167201F9EAAA900976B40 /* OrderedList.swift in Sources */,
|
||||||
|
D0FC19552020CB7700FEDBB2 /* PeerGroupStateView.swift in Sources */,
|
||||||
D0C26D721FE2E7A8004ABF18 /* GroupFeedReadStateTable.swift in Sources */,
|
D0C26D721FE2E7A8004ABF18 /* GroupFeedReadStateTable.swift in Sources */,
|
||||||
D0943AF11FD99DCD001522CC /* GroupChatListInclusion.swift in Sources */,
|
D0943AF11FD99DCD001522CC /* GroupChatListInclusion.swift in Sources */,
|
||||||
D0E3A7841B28AE0900A402D9 /* Peer.swift in Sources */,
|
D0E3A7841B28AE0900A402D9 /* Peer.swift in Sources */,
|
||||||
@ -1359,6 +1388,7 @@
|
|||||||
D04614302004E24600EC0EF2 /* LocalMessageHistoryTagsTable.swift in Sources */,
|
D04614302004E24600EC0EF2 /* LocalMessageHistoryTagsTable.swift in Sources */,
|
||||||
D00EED1E1C81F28D00341DFF /* MessageHistoryTagsTable.swift in Sources */,
|
D00EED1E1C81F28D00341DFF /* MessageHistoryTagsTable.swift in Sources */,
|
||||||
D044CA2C1C617E2D002160FF /* MessageHistoryMetadataTable.swift in Sources */,
|
D044CA2C1C617E2D002160FF /* MessageHistoryMetadataTable.swift in Sources */,
|
||||||
|
D0FC194A201E8EAF00FEDBB2 /* MediaBoxFile.swift in Sources */,
|
||||||
D03120FC1DA55427006A2A60 /* PeerNotificationSettings.swift in Sources */,
|
D03120FC1DA55427006A2A60 /* PeerNotificationSettings.swift in Sources */,
|
||||||
D0943B021FDB01D8001522CC /* PostboxUpgrade_14to15.swift in Sources */,
|
D0943B021FDB01D8001522CC /* PostboxUpgrade_14to15.swift in Sources */,
|
||||||
D0F7AB321DCFAB18009AD9A1 /* PeerChatTopIndexableMessageIds.swift in Sources */,
|
D0F7AB321DCFAB18009AD9A1 /* PeerChatTopIndexableMessageIds.swift in Sources */,
|
||||||
|
|||||||
8
Postbox/Crc32.h
Normal file
8
Postbox/Crc32.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef Postbox_Crc32_h
|
||||||
|
#define Postbox_Crc32_h
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
uint32_t Crc32(const void *bytes, int length);
|
||||||
|
|
||||||
|
#endif
|
||||||
7
Postbox/Crc32.m
Normal file
7
Postbox/Crc32.m
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#import "Crc32.h"
|
||||||
|
|
||||||
|
#import <zlib.h>
|
||||||
|
|
||||||
|
uint32_t Crc32(const void *bytes, int length) {
|
||||||
|
return (uint32_t)crc32(0, bytes, (uInt)length);
|
||||||
|
}
|
||||||
10
Postbox/FileSize.swift
Normal file
10
Postbox/FileSize.swift
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
func fileSize(_ path: String) -> Int? {
|
||||||
|
var value = stat()
|
||||||
|
if stat(path, &value) == 0 {
|
||||||
|
return Int(value.st_size)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -50,6 +50,9 @@ private func writeEntry(_ entry: GroupFeedIndexEntry, to buffer: WriteBuffer) {
|
|||||||
|
|
||||||
var stableIdValue: UInt32 = stableId
|
var stableIdValue: UInt32 = stableId
|
||||||
var timestampValue: Int32 = hole.lowerIndex.timestamp
|
var timestampValue: Int32 = hole.lowerIndex.timestamp
|
||||||
|
if timestampValue == 0 {
|
||||||
|
//print("writing 0 hole")
|
||||||
|
}
|
||||||
var idPeerIdValue: Int64 = hole.lowerIndex.id.peerId.toInt64()
|
var idPeerIdValue: Int64 = hole.lowerIndex.id.peerId.toInt64()
|
||||||
var idNamespaceValue: Int32 = hole.lowerIndex.id.namespace
|
var idNamespaceValue: Int32 = hole.lowerIndex.id.namespace
|
||||||
var idIdValue: Int32 = hole.lowerIndex.id.id
|
var idIdValue: Int32 = hole.lowerIndex.id.id
|
||||||
@ -293,6 +296,8 @@ final class GroupFeedIndexTable: Table {
|
|||||||
var filledUpperBound: MessageIndex?
|
var filledUpperBound: MessageIndex?
|
||||||
var filledLowerBound: MessageIndex?
|
var filledLowerBound: MessageIndex?
|
||||||
|
|
||||||
|
//self.debugPrintEntries(groupId: groupId)
|
||||||
|
|
||||||
var adjustedMainHoleIndex: MessageIndex?
|
var adjustedMainHoleIndex: MessageIndex?
|
||||||
do {
|
do {
|
||||||
var upperItem: GroupFeedIndexEntry?
|
var upperItem: GroupFeedIndexEntry?
|
||||||
@ -454,9 +459,13 @@ final class GroupFeedIndexTable: Table {
|
|||||||
self.fillHole(insertMessage: insertMessage, groupId: groupId, index: holeIndex, fillType: currentFillType, messages: holeMessages, addOperation: addOperation)
|
self.fillHole(insertMessage: insertMessage, groupId: groupId, index: holeIndex, fillType: currentFillType, messages: holeMessages, addOperation: addOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//self.debugPrintEntries(groupId: groupId)
|
||||||
|
|
||||||
for message in remainingMessages {
|
for message in remainingMessages {
|
||||||
insertMessage(message)
|
insertMessage(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//self.debugPrintEntries(groupId: groupId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fillHole(insertMessage: (InternalStoreMessage) -> Void, groupId: PeerGroupId, index: MessageIndex, fillType: HoleFill, messages: [InternalStoreMessage], addOperation: (PeerGroupId, GroupFeedIndexOperation) -> Void) {
|
private func fillHole(insertMessage: (InternalStoreMessage) -> Void, groupId: PeerGroupId, index: MessageIndex, fillType: HoleFill, messages: [InternalStoreMessage], addOperation: (PeerGroupId, GroupFeedIndexOperation) -> Void) {
|
||||||
@ -714,4 +723,19 @@ final class GroupFeedIndexTable: Table {
|
|||||||
}, limit: 1)
|
}, limit: 1)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func debugPrintEntries(groupId: PeerGroupId) {
|
||||||
|
print("-----------------------------")
|
||||||
|
self.valueBox.range(self.table, start: self.lowerBound(groupId: groupId), end: self.upperBound(groupId: groupId), values: { key, value in
|
||||||
|
let entry = readEntry(groupId: groupId, key: key, value: value)
|
||||||
|
switch entry {
|
||||||
|
case let .message(index):
|
||||||
|
print("message timestamp: \(index.timestamp), peerId: \(index.id.peerId.id), id: \(index.id.id)")
|
||||||
|
case let .hole(_, hole):
|
||||||
|
print("hole upper timestamp: \(hole.upperIndex.timestamp), \(hole.upperIndex.id.peerId.id), \(hole.upperIndex.id.id), lower \(hole.lowerIndex.timestamp), \(hole.lowerIndex.id.peerId.id), \(hole.lowerIndex.id.id)")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, limit: 0)
|
||||||
|
print("-----------------------------")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public final class GroupFeedState {
|
public protocol PeerGroupState: PostboxCoding {
|
||||||
|
func equals(_ other: PeerGroupState) -> Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct GroupFeedStateEntry {
|
private struct PeerGroupStateEntry {
|
||||||
let state: GroupFeedState?
|
let state: PeerGroupState?
|
||||||
|
|
||||||
init(_ state: GroupFeedState?) {
|
init(_ state: PeerGroupState?) {
|
||||||
self.state = state
|
self.state = state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class GroupFeedStateTable: Table {
|
final class PeerGroupStateTable: Table {
|
||||||
static func tableSpec(_ id: Int32) -> ValueBoxTable {
|
static func tableSpec(_ id: Int32) -> ValueBoxTable {
|
||||||
return ValueBoxTable(id: id, keyType: .int64)
|
return ValueBoxTable(id: id, keyType: .int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var cachedStates: [PeerGroupId: GroupFeedStateEntry] = [:]
|
private var cachedStates: [PeerGroupId: PeerGroupStateEntry] = [:]
|
||||||
private var updatedGroupIds = Set<PeerGroupId>()
|
private var updatedGroupIds = Set<PeerGroupId>()
|
||||||
|
|
||||||
private let sharedKey = ValueBoxKey(length: 8)
|
private let sharedKey = ValueBoxKey(length: 8)
|
||||||
@ -27,23 +27,22 @@ final class GroupFeedStateTable: Table {
|
|||||||
return self.sharedKey
|
return self.sharedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func get(_ id: PeerGroupId) -> GroupFeedState? {
|
func get(_ id: PeerGroupId) -> PeerGroupState? {
|
||||||
if let state = self.cachedStates[id] {
|
if let state = self.cachedStates[id] {
|
||||||
return state.state
|
return state.state
|
||||||
} else {
|
} else {
|
||||||
/*if let value = self.valueBox.get(self.table, key: self.key(id)), let state = PostboxDecoder(buffer: value).decodeRootObject() {
|
if let value = self.valueBox.get(self.table, key: self.key(id)), let state = PostboxDecoder(buffer: value).decodeRootObject() as? PeerGroupState {
|
||||||
self.cachedPeerChatStates[id] = state
|
self.cachedStates[id] = PeerGroupStateEntry(state)
|
||||||
return state
|
return state
|
||||||
} else {
|
} else {
|
||||||
self.cachedPeerChatStates[id] = nil
|
self.cachedStates[id] = PeerGroupStateEntry(nil)
|
||||||
return nil
|
return nil
|
||||||
}*/
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func set(_ id: PeerGroupId, state: GroupFeedState?) {
|
func set(_ id: PeerGroupId, state: PeerGroupState?) {
|
||||||
self.cachedStates[id] = GroupFeedStateEntry(state)
|
self.cachedStates[id] = PeerGroupStateEntry(state)
|
||||||
self.updatedGroupIds.insert(id)
|
self.updatedGroupIds.insert(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,10 +54,11 @@ final class GroupFeedStateTable: Table {
|
|||||||
override func beforeCommit() {
|
override func beforeCommit() {
|
||||||
if !self.updatedGroupIds.isEmpty {
|
if !self.updatedGroupIds.isEmpty {
|
||||||
for id in self.updatedGroupIds {
|
for id in self.updatedGroupIds {
|
||||||
|
let sharedEncoder = PostboxEncoder()
|
||||||
if let entry = self.cachedStates[id], let state = entry.state {
|
if let entry = self.cachedStates[id], let state = entry.state {
|
||||||
/*sharedEncoder.reset()
|
sharedEncoder.reset()
|
||||||
sharedEncoder.encodeRootObject(state)
|
sharedEncoder.encodeRootObject(state)
|
||||||
self.valueBox.set(self.table, key: self.key(id), value: sharedEncoder.readBufferNoCopy())*/
|
self.valueBox.set(self.table, key: self.key(id), value: sharedEncoder.readBufferNoCopy())
|
||||||
} else {
|
} else {
|
||||||
self.valueBox.remove(self.table, key: self.key(id))
|
self.valueBox.remove(self.table, key: self.key(id))
|
||||||
}
|
}
|
||||||
@ -68,3 +68,4 @@ final class GroupFeedStateTable: Table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -70,4 +70,17 @@ public final class ManagedFile {
|
|||||||
public func truncate(count: Int64) {
|
public func truncate(count: Int64) {
|
||||||
ftruncate(self.fd, count)
|
ftruncate(self.fd, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getSize() -> Int? {
|
||||||
|
var value = stat()
|
||||||
|
if fstat(self.fd, &value) == 0 {
|
||||||
|
return Int(value.st_size)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func sync() {
|
||||||
|
fsync(self.fd)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,11 @@ import Foundation
|
|||||||
private final class ResourceStatusContext {
|
private final class ResourceStatusContext {
|
||||||
var status: MediaResourceStatus?
|
var status: MediaResourceStatus?
|
||||||
let subscribers = Bag<(MediaResourceStatus) -> Void>()
|
let subscribers = Bag<(MediaResourceStatus) -> Void>()
|
||||||
|
let disposable: Disposable
|
||||||
|
|
||||||
|
init(disposable: Disposable) {
|
||||||
|
self.disposable = disposable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class ResourceDataContext {
|
private final class ResourceDataContext {
|
||||||
@ -24,15 +29,6 @@ private final class ResourceDataContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fileSize(_ path: String) -> Int? {
|
|
||||||
var value = stat()
|
|
||||||
if stat(path, &value) == 0 {
|
|
||||||
return Int(value.st_size)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ResourceDataRangeMode {
|
public enum ResourceDataRangeMode {
|
||||||
case complete
|
case complete
|
||||||
case incremental
|
case incremental
|
||||||
@ -51,32 +47,30 @@ private struct ResourceStorePaths {
|
|||||||
|
|
||||||
public struct MediaResourceData {
|
public struct MediaResourceData {
|
||||||
public let path: String
|
public let path: String
|
||||||
|
public let offset: Int
|
||||||
public let size: Int
|
public let size: Int
|
||||||
public let complete: Bool
|
public let complete: Bool
|
||||||
|
|
||||||
public init(path: String, size: Int, complete: Bool) {
|
public init(path: String, offset: Int, size: Int, complete: Bool) {
|
||||||
self.path = path
|
self.path = path
|
||||||
|
self.offset = offset
|
||||||
self.size = size
|
self.size = size
|
||||||
self.complete = complete
|
self.complete = complete
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MediaResourceDataFetchResult {
|
public protocol MediaResourceDataFetchCopyLocalItem {
|
||||||
case dataPart(data: Data, range: Range<Int>, complete: Bool)
|
func copyTo(url: URL) -> Bool
|
||||||
case replaceHeader(data: Data, range: Range<Int>)
|
|
||||||
case moveLocalFile(path: String)
|
|
||||||
case reset
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*public struct MediaResourceDataFetchResult {
|
public enum MediaResourceDataFetchResult {
|
||||||
public let data: Data
|
case dataPart(resourceOffset: Int, data: Data, range: Range<Int>, complete: Bool)
|
||||||
public let complete: Bool
|
case resourceSizeUpdated(Int)
|
||||||
|
case replaceHeader(data: Data, range: Range<Int>)
|
||||||
public init(data: Data, complete: Bool) {
|
case moveLocalFile(path: String)
|
||||||
self.data = data
|
case copyLocalItem(MediaResourceDataFetchCopyLocalItem)
|
||||||
self.complete = complete
|
case reset
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
|
|
||||||
public struct CachedMediaResourceRepresentationResult {
|
public struct CachedMediaResourceRepresentationResult {
|
||||||
public let temporaryPath: String
|
public let temporaryPath: String
|
||||||
@ -119,12 +113,12 @@ public final class MediaBox {
|
|||||||
private let cacheQueue = Queue()
|
private let cacheQueue = Queue()
|
||||||
|
|
||||||
private var statusContexts: [WrappedMediaResourceId: ResourceStatusContext] = [:]
|
private var statusContexts: [WrappedMediaResourceId: ResourceStatusContext] = [:]
|
||||||
private var dataContexts: [WrappedMediaResourceId: ResourceDataContext] = [:]
|
|
||||||
private var randomAccessContexts: [WrappedMediaResourceId: RandomAccessMediaResourceContext] = [:]
|
|
||||||
private var cachedRepresentationContexts: [CachedMediaResourceRepresentationKey: CachedMediaResourceRepresentationContext] = [:]
|
private var cachedRepresentationContexts: [CachedMediaResourceRepresentationKey: CachedMediaResourceRepresentationContext] = [:]
|
||||||
|
|
||||||
private var wrappedFetchResource = Promise<(MediaResource, Range<Int>, MediaResourceFetchTag?) -> Signal<MediaResourceDataFetchResult, NoError>>()
|
private var fileContexts: [WrappedMediaResourceId: MediaBoxFileContext] = [:]
|
||||||
public var fetchResource: ((MediaResource, Range<Int>, MediaResourceFetchTag?) -> Signal<MediaResourceDataFetchResult, NoError>)? {
|
|
||||||
|
private var wrappedFetchResource = Promise<(MediaResource, Signal<IndexSet, NoError>, MediaResourceFetchTag?) -> Signal<MediaResourceDataFetchResult, NoError>>()
|
||||||
|
public var fetchResource: ((MediaResource, Signal<IndexSet, NoError>, MediaResourceFetchTag?) -> Signal<MediaResourceDataFetchResult, NoError>)? {
|
||||||
didSet {
|
didSet {
|
||||||
if let fetchResource = self.fetchResource {
|
if let fetchResource = self.fetchResource {
|
||||||
wrappedFetchResource.set(.single(fetchResource))
|
wrappedFetchResource.set(.single(fetchResource))
|
||||||
@ -199,12 +193,16 @@ public final class MediaBox {
|
|||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
} else {
|
} else {
|
||||||
self.statusQueue.async {
|
self.statusQueue.async {
|
||||||
|
let resourceId = WrappedMediaResourceId(resource.id)
|
||||||
let statusContext: ResourceStatusContext
|
let statusContext: ResourceStatusContext
|
||||||
if let current = self.statusContexts[WrappedMediaResourceId(resource.id)] {
|
var statusUpdateDisposable: MetaDisposable?
|
||||||
|
if let current = self.statusContexts[resourceId] {
|
||||||
statusContext = current
|
statusContext = current
|
||||||
} else {
|
} else {
|
||||||
statusContext = ResourceStatusContext()
|
let statusUpdateDisposableValue = MetaDisposable()
|
||||||
self.statusContexts[WrappedMediaResourceId(resource.id)] = statusContext
|
statusContext = ResourceStatusContext(disposable: statusUpdateDisposableValue)
|
||||||
|
self.statusContexts[resourceId] = statusContext
|
||||||
|
statusUpdateDisposable = statusUpdateDisposableValue
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = statusContext.subscribers.add({ status in
|
let index = statusContext.subscribers.add({ status in
|
||||||
@ -213,40 +211,32 @@ public final class MediaBox {
|
|||||||
|
|
||||||
if let status = statusContext.status {
|
if let status = statusContext.status {
|
||||||
subscriber.putNext(status)
|
subscriber.putNext(status)
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if let statusUpdateDisposable = statusUpdateDisposable {
|
||||||
|
let statusQueue = self.statusQueue
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
let status: MediaResourceStatus
|
if let fileContext = self.fileContext(for: resource) {
|
||||||
|
statusUpdateDisposable.set(fileContext.status(next: { value in
|
||||||
if let _ = fileSize(paths.complete) {
|
statusQueue.async {
|
||||||
status = .Local
|
if let context = self.statusContexts[resourceId], context.status != value {
|
||||||
} else {
|
context.status = value
|
||||||
var fetchingData = false
|
for subscriber in statusContext.subscribers.copyItems() {
|
||||||
if let dataContext = self.dataContexts[WrappedMediaResourceId(resource.id)] {
|
subscriber(value)
|
||||||
fetchingData = dataContext.fetchDisposable != nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fetchingData {
|
|
||||||
let currentSize = fileSize(paths.partial) ?? 0
|
|
||||||
|
|
||||||
if let resourceSize = resource.size {
|
|
||||||
status = .Fetching(isActive: true, progress: Float(currentSize) / Float(resourceSize))
|
|
||||||
} else {
|
|
||||||
status = .Fetching(isActive: true, progress: 0.0)
|
|
||||||
}
|
}
|
||||||
|
}, completed: {
|
||||||
} else {
|
statusQueue.async {
|
||||||
status = .Remote
|
if let context = self.statusContexts[resourceId] {
|
||||||
}
|
context.subscribers.remove(index)
|
||||||
}
|
if context.subscribers.isEmpty {
|
||||||
|
self.statusContexts.removeValue(forKey: resourceId)
|
||||||
self.statusQueue.async {
|
context.disposable.dispose()
|
||||||
if let statusContext = self.statusContexts[WrappedMediaResourceId(resource.id)] , statusContext.status == nil {
|
}
|
||||||
statusContext.status = status
|
}
|
||||||
|
|
||||||
for subscriber in statusContext.subscribers.copyItems() {
|
|
||||||
subscriber(status)
|
|
||||||
}
|
}
|
||||||
}
|
}, size: resource.size.flatMap(Int32.init)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,6 +247,7 @@ public final class MediaBox {
|
|||||||
current.subscribers.remove(index)
|
current.subscribers.remove(index)
|
||||||
if current.subscribers.isEmpty {
|
if current.subscribers.isEmpty {
|
||||||
self.statusContexts.removeValue(forKey: WrappedMediaResourceId(resource.id))
|
self.statusContexts.removeValue(forKey: WrappedMediaResourceId(resource.id))
|
||||||
|
current.disposable.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,23 +288,53 @@ public final class MediaBox {
|
|||||||
if fileSize(symlinkPath) == nil {
|
if fileSize(symlinkPath) == nil {
|
||||||
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
||||||
}
|
}
|
||||||
subscriber.putNext(MediaResourceData(path: symlinkPath, size: completeSize, complete: true))
|
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: completeSize, complete: true))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
} else {
|
} else {
|
||||||
subscriber.putNext(MediaResourceData(path: paths.complete, size: completeSize, complete: true))
|
subscriber.putNext(MediaResourceData(path: paths.complete, offset: 0, size: completeSize, complete: true))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
let resourceId = WrappedMediaResourceId(resource.id)
|
if let fileContext = self.fileContext(for: resource) {
|
||||||
let currentContext: ResourceDataContext? = self.dataContexts[resourceId]
|
let waitUntilAfterInitialFetch: Bool
|
||||||
|
switch option {
|
||||||
|
case let .complete(waitUntilFetchStatus):
|
||||||
|
waitUntilAfterInitialFetch = waitUntilFetchStatus
|
||||||
|
case let .incremental(waitUntilFetchStatus):
|
||||||
|
waitUntilAfterInitialFetch = waitUntilFetchStatus
|
||||||
|
}
|
||||||
|
let dataDisposable = fileContext.data(range: 0 ..< Int32.max, waitUntilAfterInitialFetch: waitUntilAfterInitialFetch, next: { value in
|
||||||
|
self.dataQueue.async {
|
||||||
|
if value.complete {
|
||||||
|
if let pathExtension = pathExtension {
|
||||||
|
let symlinkPath = paths.complete + ".\(pathExtension)"
|
||||||
|
if fileSize(symlinkPath) == nil {
|
||||||
|
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
||||||
|
}
|
||||||
|
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: value.size, complete: true))
|
||||||
|
} else {
|
||||||
|
subscriber.putNext(value)
|
||||||
|
}
|
||||||
|
subscriber.putCompletion()
|
||||||
|
} else {
|
||||||
|
subscriber.putNext(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
disposable.set(ActionDisposable {
|
||||||
|
dataDisposable.dispose()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*let currentContext: ResourceDataContext? = self.dataContexts[resourceId]
|
||||||
if let currentContext = currentContext, currentContext.data.complete {
|
if let currentContext = currentContext, currentContext.data.complete {
|
||||||
if let pathExtension = pathExtension {
|
if let pathExtension = pathExtension {
|
||||||
let symlinkPath = paths.complete + ".\(pathExtension)"
|
let symlinkPath = paths.complete + ".\(pathExtension)"
|
||||||
if fileSize(symlinkPath) == nil {
|
if fileSize(symlinkPath) == nil {
|
||||||
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
||||||
}
|
}
|
||||||
subscriber.putNext(MediaResourceData(path: symlinkPath, size: currentContext.data.size, complete: currentContext.data.complete))
|
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: currentContext.data.size, complete: currentContext.data.complete))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
} else {
|
} else {
|
||||||
subscriber.putNext(currentContext.data)
|
subscriber.putNext(currentContext.data)
|
||||||
@ -325,10 +346,10 @@ public final class MediaBox {
|
|||||||
if fileSize(symlinkPath) == nil {
|
if fileSize(symlinkPath) == nil {
|
||||||
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
||||||
}
|
}
|
||||||
subscriber.putNext(MediaResourceData(path: symlinkPath, size: completeSize, complete: true))
|
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: completeSize, complete: true))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
} else {
|
} else {
|
||||||
subscriber.putNext(MediaResourceData(path: paths.complete, size: completeSize, complete: true))
|
subscriber.putNext(MediaResourceData(path: paths.complete, offset: 0, size: completeSize, complete: true))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -337,7 +358,7 @@ public final class MediaBox {
|
|||||||
dataContext = currentContext
|
dataContext = currentContext
|
||||||
} else {
|
} else {
|
||||||
let partialSize = fileSize(paths.partial) ?? 0
|
let partialSize = fileSize(paths.partial) ?? 0
|
||||||
dataContext = ResourceDataContext(data: MediaResourceData(path: paths.partial, size: partialSize, complete: false))
|
dataContext = ResourceDataContext(data: MediaResourceData(path: paths.partial, offset: 0, size: partialSize, complete: false))
|
||||||
self.dataContexts[resourceId] = dataContext
|
self.dataContexts[resourceId] = dataContext
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,7 +371,7 @@ public final class MediaBox {
|
|||||||
if fileSize(symlinkPath) == nil {
|
if fileSize(symlinkPath) == nil {
|
||||||
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
||||||
}
|
}
|
||||||
subscriber.putNext(MediaResourceData(path: symlinkPath, size: data.size, complete: data.complete))
|
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: data.size, complete: data.complete))
|
||||||
if data.complete {
|
if data.complete {
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
}
|
}
|
||||||
@ -360,7 +381,7 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
if !waitUntilFetchStatus || dataContext.processedFetch {
|
if !waitUntilFetchStatus || dataContext.processedFetch {
|
||||||
subscriber.putNext(MediaResourceData(path: dataContext.data.path, size: 0, complete: false))
|
subscriber.putNext(MediaResourceData(path: dataContext.data.path, offset: 0, size: 0, complete: false))
|
||||||
}
|
}
|
||||||
case let .incremental(waitUntilFetchStatus):
|
case let .incremental(waitUntilFetchStatus):
|
||||||
index = dataContext.progresiveDataSubscribers.add((waitUntilFetchStatus, { data in
|
index = dataContext.progresiveDataSubscribers.add((waitUntilFetchStatus, { data in
|
||||||
@ -369,7 +390,7 @@ public final class MediaBox {
|
|||||||
if fileSize(symlinkPath) == nil {
|
if fileSize(symlinkPath) == nil {
|
||||||
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
|
||||||
}
|
}
|
||||||
subscriber.putNext(MediaResourceData(path: symlinkPath, size: data.size, complete: data.complete))
|
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: data.size, complete: data.complete))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
} else {
|
} else {
|
||||||
subscriber.putNext(data)
|
subscriber.putNext(data)
|
||||||
@ -399,7 +420,7 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -408,72 +429,42 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func randomAccessContext(for resource: MediaResource, size: Int, tag: MediaResourceFetchTag?) -> RandomAccessMediaResourceContext {
|
private func fileContext(for resource: MediaResource) -> MediaBoxFileContext? {
|
||||||
assert(self.dataQueue.isCurrent())
|
assert(self.dataQueue.isCurrent())
|
||||||
|
|
||||||
let resourceId = WrappedMediaResourceId(resource.id)
|
let resourceId = WrappedMediaResourceId(resource.id)
|
||||||
|
|
||||||
let dataContext: RandomAccessMediaResourceContext
|
if let current = self.fileContexts[resourceId] {
|
||||||
if let current = self.randomAccessContexts[resourceId] {
|
return current
|
||||||
dataContext = current
|
|
||||||
} else {
|
} else {
|
||||||
let path = self.pathForId(resource.id) + ".random"
|
let paths = self.storePathsForId(resource.id)
|
||||||
dataContext = RandomAccessMediaResourceContext(path: path, size: size, fetchRange: { [weak self] range in
|
if let fileContext = MediaBoxFileContext(queue: self.dataQueue, path: paths.complete, partialPath: paths.partial) {
|
||||||
let disposable = MetaDisposable()
|
self.fileContexts[resourceId] = fileContext
|
||||||
|
return fileContext
|
||||||
if let strongSelf = self {
|
} else {
|
||||||
strongSelf.dataQueue.async {
|
return nil
|
||||||
let fetch = strongSelf.wrappedFetchResource.get() |> take(1) |> mapToSignal { fetch -> Signal<MediaResourceDataFetchResult, NoError> in
|
}
|
||||||
return fetch(resource, range, tag)
|
|
||||||
}
|
|
||||||
var offset = 0
|
|
||||||
disposable.set(fetch.start(next: { [weak strongSelf] result in
|
|
||||||
if let strongSelf = strongSelf {
|
|
||||||
strongSelf.dataQueue.async {
|
|
||||||
if let dataContext = strongSelf.randomAccessContexts[resourceId] {
|
|
||||||
switch result {
|
|
||||||
case let .dataPart(data, dataRange, _):
|
|
||||||
let storeRange = RandomAccessResourceStoreRange(offset: range.lowerBound + offset, data: data.subdata(in: dataRange))
|
|
||||||
offset += data.count
|
|
||||||
dataContext.storeRanges([storeRange])
|
|
||||||
default:
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return disposable
|
|
||||||
})
|
|
||||||
self.randomAccessContexts[resourceId] = dataContext
|
|
||||||
}
|
}
|
||||||
return dataContext
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchedResourceData(_ resource: MediaResource, size: Int, in range: Range<Int>, tag: MediaResourceFetchTag?) -> Signal<Void, NoError> {
|
public func fetchedResourceData(_ resource: MediaResource, in range: Range<Int>, tag: MediaResourceFetchTag?) -> Signal<Void, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
let resourceId = WrappedMediaResourceId(resource.id)
|
let fileContext = self.fileContext(for: resource)
|
||||||
let dataContext = self.randomAccessContext(for: resource, size: size, tag: tag)
|
|
||||||
|
|
||||||
let listener = dataContext.addListenerForFetchedData(in: range)
|
let fetchResource = self.wrappedFetchResource.get()
|
||||||
|
let fetchedDisposable = fileContext?.fetched(range: Int32(range.lowerBound) ..< Int32(range.upperBound), fetch: { ranges in
|
||||||
disposable.set(ActionDisposable { [weak self] in
|
return fetchResource |> mapToSignal { fetch in
|
||||||
if let strongSelf = self {
|
return fetch(resource, ranges, tag)
|
||||||
strongSelf.dataQueue.async {
|
|
||||||
if let dataContext = strongSelf.randomAccessContexts[resourceId] {
|
|
||||||
dataContext.removeListenerForFetchedData(listener)
|
|
||||||
if !dataContext.hasDataListeners() {
|
|
||||||
//let _ = strongSelf.randomAccessContexts.removeValue(forKey: resourceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}, completed: {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
|
||||||
|
disposable.set(ActionDisposable {
|
||||||
|
fetchedDisposable?.dispose()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,55 +472,56 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func resourceData(_ resource: MediaResource, size: Int, in range: Range<Int>, tag: MediaResourceFetchTag?, mode: ResourceDataRangeMode = .complete) -> Signal<Data, NoError> {
|
public func resourceData(_ resource: MediaResource, size: Int, in range: Range<Int>, mode: ResourceDataRangeMode = .complete) -> Signal<Data, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
let resourceId = WrappedMediaResourceId(resource.id)
|
let fileContext = self.fileContext(for: resource)
|
||||||
let dataContext = self.randomAccessContext(for: resource, size: size, tag: tag)
|
|
||||||
|
|
||||||
let listenerMode: RandomAccessResourceDataRangeMode
|
|
||||||
switch mode {
|
|
||||||
case .complete:
|
|
||||||
listenerMode = .Complete
|
|
||||||
case .incremental:
|
|
||||||
listenerMode = .Incremental
|
|
||||||
case .partial:
|
|
||||||
listenerMode = .Partial
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset = 0
|
|
||||||
|
|
||||||
let listener = dataContext.addListenerForData(in: range, mode: listenerMode, updated: { [weak self] data in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.dataQueue.async {
|
|
||||||
subscriber.putNext(data)
|
|
||||||
|
|
||||||
|
let dataDisposable = fileContext?.data(range: Int32(range.lowerBound) ..< Int32(range.upperBound), waitUntilAfterInitialFetch: false, next: { result in
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: result.path), options: .mappedRead) {
|
||||||
|
if result.complete {
|
||||||
|
let resultData = data.subdata(in: result.offset ..< (result.offset + result.size))
|
||||||
|
subscriber.putNext(resultData)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
} else {
|
||||||
switch mode {
|
switch mode {
|
||||||
case .complete, .partial:
|
case .complete:
|
||||||
offset = max(offset, data.count)
|
break
|
||||||
case .incremental:
|
case .incremental:
|
||||||
offset += data.count
|
break
|
||||||
}
|
case .partial:
|
||||||
if offset == range.count {
|
break
|
||||||
subscriber.putCompletion()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
disposable.set(ActionDisposable { [weak self] in
|
disposable.set(ActionDisposable {
|
||||||
if let strongSelf = self {
|
dataDisposable?.dispose()
|
||||||
strongSelf.dataQueue.async {
|
})
|
||||||
if let dataContext = strongSelf.randomAccessContexts[resourceId] {
|
}
|
||||||
dataContext.removeListenerForData(listener)
|
|
||||||
if !dataContext.hasDataListeners() {
|
return disposable
|
||||||
//let _ = strongSelf.randomAccessContexts.removeValue(forKey: resourceId)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
public func resourceRangesStatus(_ resource: MediaResource) -> Signal<IndexSet, NoError> {
|
||||||
}
|
return Signal { subscriber in
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
self.dataQueue.async {
|
||||||
|
let fileContext = self.fileContext(for: resource)
|
||||||
|
|
||||||
|
let statusDisposable = fileContext?.rangeStatus(next: { result in
|
||||||
|
subscriber.putNext(result)
|
||||||
|
}, completed: {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
|
||||||
|
disposable.set(ActionDisposable {
|
||||||
|
statusDisposable?.dispose()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,7 +534,6 @@ public final class MediaBox {
|
|||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
let resourceId = WrappedMediaResourceId(resource.id)
|
|
||||||
let paths = self.storePathsForId(resource.id)
|
let paths = self.storePathsForId(resource.id)
|
||||||
|
|
||||||
if let _ = fileSize(paths.complete) {
|
if let _ = fileSize(paths.complete) {
|
||||||
@ -551,12 +542,27 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
} else {
|
} else {
|
||||||
|
if let fileContext = self.fileContext(for: resource) {
|
||||||
|
let fetchResource = self.wrappedFetchResource.get()
|
||||||
|
let fetchedDisposable = fileContext.fetchedFullRange(fetch: { ranges in
|
||||||
|
return fetchResource |> mapToSignal { fetch in
|
||||||
|
return fetch(resource, ranges, tag)
|
||||||
|
}
|
||||||
|
}, completed: {
|
||||||
|
if implNext {
|
||||||
|
subscriber.putNext(.remote)
|
||||||
|
}
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
disposable.set(fetchedDisposable)
|
||||||
|
}
|
||||||
|
/*
|
||||||
let currentSize = fileSize(paths.partial) ?? 0
|
let currentSize = fileSize(paths.partial) ?? 0
|
||||||
let dataContext: ResourceDataContext
|
let dataContext: ResourceDataContext
|
||||||
if let current = self.dataContexts[resourceId] {
|
if let current = self.dataContexts[resourceId] {
|
||||||
dataContext = current
|
dataContext = current
|
||||||
} else {
|
} else {
|
||||||
dataContext = ResourceDataContext(data: MediaResourceData(path: paths.partial, size: currentSize, complete: false))
|
dataContext = ResourceDataContext(data: MediaResourceData(path: paths.partial, offset: 0, size: currentSize, complete: false))
|
||||||
self.dataContexts[resourceId] = dataContext
|
self.dataContexts[resourceId] = dataContext
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -582,7 +588,9 @@ public final class MediaBox {
|
|||||||
let file = Atomic<ManagedFile?>(value: nil)
|
let file = Atomic<ManagedFile?>(value: nil)
|
||||||
let dataQueue = self.dataQueue
|
let dataQueue = self.dataQueue
|
||||||
dataContext.fetchDisposable = ((self.wrappedFetchResource.get() |> take(1) |> mapToSignal { fetch -> Signal<MediaResourceDataFetchResult, NoError> in
|
dataContext.fetchDisposable = ((self.wrappedFetchResource.get() |> take(1) |> mapToSignal { fetch -> Signal<MediaResourceDataFetchResult, NoError> in
|
||||||
return fetch(resource, currentSize ..< Int.max, tag)
|
var ranges = IndexSet()
|
||||||
|
ranges.insert(integersIn: currentSize ..< Int.max)
|
||||||
|
return fetch(resource, .single(ranges), tag)
|
||||||
}) |> afterDisposed {
|
}) |> afterDisposed {
|
||||||
dataQueue.async {
|
dataQueue.async {
|
||||||
let _ = file.modify { current in
|
let _ = file.modify { current in
|
||||||
@ -594,7 +602,9 @@ public final class MediaBox {
|
|||||||
let _ = self.ensureDirectoryCreated
|
let _ = self.ensureDirectoryCreated
|
||||||
|
|
||||||
switch resultOption {
|
switch resultOption {
|
||||||
case let .dataPart(data, dataRange, complete):
|
case .resourceSizeUpdated:
|
||||||
|
break
|
||||||
|
case let .dataPart(_, data, dataRange, complete):
|
||||||
var currentFile: ManagedFile?
|
var currentFile: ManagedFile?
|
||||||
let _ = file.modify { current in
|
let _ = file.modify { current in
|
||||||
if let current = current {
|
if let current = current {
|
||||||
@ -624,9 +634,9 @@ public final class MediaBox {
|
|||||||
if complete {
|
if complete {
|
||||||
let linkResult = link(paths.partial, paths.complete)
|
let linkResult = link(paths.partial, paths.complete)
|
||||||
//assert(linkResult == 0)
|
//assert(linkResult == 0)
|
||||||
updatedData = MediaResourceData(path: paths.complete, size: updatedSize, complete: true)
|
updatedData = MediaResourceData(path: paths.complete, offset: 0, size: updatedSize, complete: true)
|
||||||
} else {
|
} else {
|
||||||
updatedData = MediaResourceData(path: paths.partial, size: updatedSize, complete: false)
|
updatedData = MediaResourceData(path: paths.partial, offset: 0, size: updatedSize, complete: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
dataContext.data = updatedData
|
dataContext.data = updatedData
|
||||||
@ -716,7 +726,7 @@ public final class MediaBox {
|
|||||||
let updatedSize = offset
|
let updatedSize = offset
|
||||||
|
|
||||||
let updatedData: MediaResourceData
|
let updatedData: MediaResourceData
|
||||||
updatedData = MediaResourceData(path: paths.partial, size: updatedSize, complete: false)
|
updatedData = MediaResourceData(path: paths.partial, offset: 0, size: updatedSize, complete: false)
|
||||||
|
|
||||||
dataContext.data = updatedData
|
dataContext.data = updatedData
|
||||||
|
|
||||||
@ -779,7 +789,7 @@ public final class MediaBox {
|
|||||||
let updatedData: MediaResourceData
|
let updatedData: MediaResourceData
|
||||||
let linkResult = link(paths.partial, paths.complete)
|
let linkResult = link(paths.partial, paths.complete)
|
||||||
assert(linkResult == 0)
|
assert(linkResult == 0)
|
||||||
updatedData = MediaResourceData(path: paths.complete, size: updatedSize, complete: true)
|
updatedData = MediaResourceData(path: paths.complete, offset: 0, size: updatedSize, complete: true)
|
||||||
|
|
||||||
dataContext.data = updatedData
|
dataContext.data = updatedData
|
||||||
|
|
||||||
@ -857,7 +867,7 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -867,7 +877,10 @@ public final class MediaBox {
|
|||||||
|
|
||||||
public func cancelInteractiveResourceFetch(_ resource: MediaResource) {
|
public func cancelInteractiveResourceFetch(_ resource: MediaResource) {
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
let resourceId = WrappedMediaResourceId(resource.id)
|
if let fileContext = self.fileContext(for: resource) {
|
||||||
|
fileContext.cancelFullRangeFetches()
|
||||||
|
}
|
||||||
|
/*let resourceId = WrappedMediaResourceId(resource.id)
|
||||||
if let dataContext = self.dataContexts[resourceId], dataContext.fetchDisposable != nil {
|
if let dataContext = self.dataContexts[resourceId], dataContext.fetchDisposable != nil {
|
||||||
dataContext.fetchDisposable?.dispose()
|
dataContext.fetchDisposable?.dispose()
|
||||||
dataContext.fetchDisposable = nil
|
dataContext.fetchDisposable = nil
|
||||||
@ -887,7 +900,7 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -897,7 +910,7 @@ public final class MediaBox {
|
|||||||
self.concurrentQueue.async {
|
self.concurrentQueue.async {
|
||||||
let path = self.cachedRepresentationPathForId(resource.id, representation: representation)
|
let path = self.cachedRepresentationPathForId(resource.id, representation: representation)
|
||||||
if let size = fileSize(path) {
|
if let size = fileSize(path) {
|
||||||
subscriber.putNext(MediaResourceData(path: path, size: size, complete: true))
|
subscriber.putNext(MediaResourceData(path: path, offset: 0, size: size, complete: true))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
} else {
|
} else {
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
@ -961,7 +974,7 @@ public final class MediaBox {
|
|||||||
if let strongSelf = self, let context = strongSelf.cachedRepresentationContexts[key] {
|
if let strongSelf = self, let context = strongSelf.cachedRepresentationContexts[key] {
|
||||||
strongSelf.cachedRepresentationContexts.removeValue(forKey: key)
|
strongSelf.cachedRepresentationContexts.removeValue(forKey: key)
|
||||||
if let size = fileSize(path) {
|
if let size = fileSize(path) {
|
||||||
let data = MediaResourceData(path: path, size: size, complete: true)
|
let data = MediaResourceData(path: path, offset: 0, size: size, complete: true)
|
||||||
context.currentData = data
|
context.currentData = data
|
||||||
for subscriber in context.dataSubscribers.copyItems() {
|
for subscriber in context.dataSubscribers.copyItems() {
|
||||||
subscriber(data)
|
subscriber(data)
|
||||||
@ -970,7 +983,7 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let strongSelf = self, let context = strongSelf.cachedRepresentationContexts[key] {
|
if let strongSelf = self, let context = strongSelf.cachedRepresentationContexts[key] {
|
||||||
let data = MediaResourceData(path: path, size: 0, complete: false)
|
let data = MediaResourceData(path: path, offset: 0, size: 0, complete: false)
|
||||||
context.currentData = data
|
context.currentData = data
|
||||||
for subscriber in context.dataSubscribers.copyItems() {
|
for subscriber in context.dataSubscribers.copyItems() {
|
||||||
subscriber(data)
|
subscriber(data)
|
||||||
@ -1006,6 +1019,87 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func collectOtherResourceUsage(excludeIds: Set<WrappedMediaResourceId>) -> Signal<(Int64, [String], Int64), NoError> {
|
||||||
|
return Signal { subscriber in
|
||||||
|
self.dataQueue.async {
|
||||||
|
var result: Int64 = 0
|
||||||
|
|
||||||
|
var excludeNames = Set<String>()
|
||||||
|
for id in excludeIds {
|
||||||
|
let partial = "\(self.fileNameForId(id.id))_partial"
|
||||||
|
let meta = "\(self.fileNameForId(id.id))_meta"
|
||||||
|
let complete = self.fileNameForId(id.id)
|
||||||
|
|
||||||
|
excludeNames.insert(meta)
|
||||||
|
excludeNames.insert(partial)
|
||||||
|
excludeNames.insert(complete)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileIds = Set<Data>()
|
||||||
|
|
||||||
|
var paths: [String] = []
|
||||||
|
|
||||||
|
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [.fileSizeKey, .fileResourceIdentifierKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
||||||
|
loop: for url in enumerator {
|
||||||
|
if let url = url as? URL {
|
||||||
|
if excludeNames.contains(url.lastPathComponent) {
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
|
|
||||||
|
if let fileId = (try? url.resourceValues(forKeys: Set([.fileResourceIdentifierKey])))?.fileResourceIdentifier as? Data {
|
||||||
|
if fileIds.contains(fileId) {
|
||||||
|
paths.append(url.lastPathComponent)
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
|
|
||||||
|
if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 {
|
||||||
|
fileIds.insert(fileId)
|
||||||
|
paths.append(url.lastPathComponent)
|
||||||
|
result += Int64(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cacheResult: Int64 = 0
|
||||||
|
|
||||||
|
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: self.basePath + "/cache"), includingPropertiesForKeys: [.fileSizeKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
||||||
|
loop: for url in enumerator {
|
||||||
|
if let url = url as? URL {
|
||||||
|
if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 {
|
||||||
|
cacheResult += Int64(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriber.putNext((result, paths, cacheResult))
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeOtherCachedResources(paths: [String]) -> Signal<Void, NoError> {
|
||||||
|
return Signal { subscriber in
|
||||||
|
self.dataQueue.async {
|
||||||
|
for path in paths {
|
||||||
|
unlink(self.basePath + "/" + path)
|
||||||
|
}
|
||||||
|
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: self.basePath + "/cache"), includingPropertiesForKeys: [], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
||||||
|
loop: for url in enumerator {
|
||||||
|
if let url = url as? URL {
|
||||||
|
unlink(url.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func removeCachedResources(_ ids: Set<WrappedMediaResourceId>) -> Signal<Void, NoError> {
|
public func removeCachedResources(_ ids: Set<WrappedMediaResourceId>) -> Signal<Void, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
@ -1014,6 +1108,7 @@ public final class MediaBox {
|
|||||||
unlink(paths.complete)
|
unlink(paths.complete)
|
||||||
unlink(paths.partial)
|
unlink(paths.partial)
|
||||||
}
|
}
|
||||||
|
subscriber.putCompletion()
|
||||||
}
|
}
|
||||||
return EmptyDisposable
|
return EmptyDisposable
|
||||||
}
|
}
|
||||||
|
|||||||
822
Postbox/MediaBoxFile.swift
Normal file
822
Postbox/MediaBoxFile.swift
Normal file
@ -0,0 +1,822 @@
|
|||||||
|
import Foundation
|
||||||
|
#if os(iOS)
|
||||||
|
import SwiftSignalKit
|
||||||
|
#else
|
||||||
|
import SwiftSignalKitMac
|
||||||
|
#endif
|
||||||
|
|
||||||
|
import sqlcipher
|
||||||
|
|
||||||
|
private final class MediaBoxFileMap {
|
||||||
|
fileprivate(set) var sum: Int32
|
||||||
|
private(set) var ranges: IndexSet
|
||||||
|
private(set) var truncationSize: Int32?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.sum = 0
|
||||||
|
self.ranges = IndexSet()
|
||||||
|
self.truncationSize = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
init?(fd: ManagedFile) {
|
||||||
|
guard let length = fd.getSize() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var crc: UInt32 = 0
|
||||||
|
var count: Int32 = 0
|
||||||
|
var sum: Int32 = 0
|
||||||
|
var ranges: IndexSet = IndexSet()
|
||||||
|
|
||||||
|
guard fd.read(&crc, 4) == 4 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard fd.read(&count, 4) == 4 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if count < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if count < 0 || length < 4 + 4 + count * 2 * 4 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var truncationSizeValue: Int32 = 0
|
||||||
|
|
||||||
|
var data = Data(count: Int(4 + count * 2 * 4))
|
||||||
|
if !(data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Bool in
|
||||||
|
guard fd.read(bytes, data.count) == data.count else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&truncationSizeValue, bytes, 4)
|
||||||
|
|
||||||
|
let calculatedCrc = Crc32(bytes, Int32(data.count))
|
||||||
|
if calculatedCrc != crc {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = 4
|
||||||
|
for _ in 0 ..< count {
|
||||||
|
var intervalOffset: Int32 = 0
|
||||||
|
var intervalLength: Int32 = 0
|
||||||
|
memcpy(&intervalOffset, bytes.advanced(by: offset), 4)
|
||||||
|
memcpy(&intervalLength, bytes.advanced(by: offset + 4), 4)
|
||||||
|
offset += 8
|
||||||
|
|
||||||
|
ranges.insert(integersIn: Int(intervalOffset) ..< Int(intervalOffset + intervalLength))
|
||||||
|
|
||||||
|
sum += intervalLength
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sum = sum
|
||||||
|
self.ranges = ranges
|
||||||
|
if truncationSizeValue == -1 {
|
||||||
|
self.truncationSize = nil
|
||||||
|
} else {
|
||||||
|
self.truncationSize = truncationSizeValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialize(to file: ManagedFile) {
|
||||||
|
file.seek(position: 0)
|
||||||
|
let buffer = WriteBuffer()
|
||||||
|
var zero: Int32 = 0
|
||||||
|
buffer.write(&zero, offset: 0, length: 4)
|
||||||
|
|
||||||
|
let rangeView = self.ranges.rangeView
|
||||||
|
var count: Int32 = Int32(rangeView.count)
|
||||||
|
buffer.write(&count, offset: 0, length: 4)
|
||||||
|
|
||||||
|
var truncationSizeValue: Int32 = self.truncationSize ?? -1
|
||||||
|
buffer.write(&truncationSizeValue, offset: 0, length: 4)
|
||||||
|
|
||||||
|
for range in rangeView {
|
||||||
|
var intervalOffset = Int32(range.lowerBound)
|
||||||
|
var intervalLength = Int32(range.count)
|
||||||
|
buffer.write(&intervalOffset, offset: 0, length: 4)
|
||||||
|
buffer.write(&intervalLength, offset: 0, length: 4)
|
||||||
|
}
|
||||||
|
var crc: UInt32 = Crc32(buffer.memory.advanced(by: 4 * 2), Int32(buffer.length - 4 * 2))
|
||||||
|
memcpy(buffer.memory, &crc, 4)
|
||||||
|
let written = file.write(buffer.memory, count: buffer.length)
|
||||||
|
assert(written == buffer.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func fill(_ range: Range<Int32>) {
|
||||||
|
let intRange = Range(Int(range.lowerBound) ..< Int(range.upperBound))
|
||||||
|
let previousCount = self.ranges.count(in: intRange)
|
||||||
|
self.ranges.insert(integersIn: intRange)
|
||||||
|
self.sum += Int32(range.count - previousCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func truncate(_ size: Int32) {
|
||||||
|
self.truncationSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func reset() {
|
||||||
|
self.truncationSize = nil
|
||||||
|
self.ranges.removeAll()
|
||||||
|
self.sum = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func contains(_ range: Range<Int32>) -> Bool {
|
||||||
|
let maxValue: Int
|
||||||
|
if let truncationSize = self.truncationSize {
|
||||||
|
maxValue = Int(truncationSize)
|
||||||
|
} else {
|
||||||
|
maxValue = Int.max
|
||||||
|
}
|
||||||
|
let intRange = Range(Int(range.lowerBound) ..< min(maxValue, Int(range.upperBound)))
|
||||||
|
return self.ranges.contains(integersIn: intRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MediaBoxPartialFileDataRequest {
|
||||||
|
let range: Range<Int32>
|
||||||
|
var waitingUntilAfterInitialFetch: Bool
|
||||||
|
let completion: (MediaResourceData) -> Void
|
||||||
|
|
||||||
|
init(range: Range<Int32>, waitingUntilAfterInitialFetch: Bool, completion: @escaping (MediaResourceData) -> Void) {
|
||||||
|
self.range = range
|
||||||
|
self.waitingUntilAfterInitialFetch = waitingUntilAfterInitialFetch
|
||||||
|
self.completion = completion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class MediaBoxPartialFile {
|
||||||
|
private let queue: Queue
|
||||||
|
private let path: String
|
||||||
|
private let completePath: String
|
||||||
|
private let completed: (Int32) -> Void
|
||||||
|
private let metadataFd: ManagedFile
|
||||||
|
private let fd: ManagedFile
|
||||||
|
fileprivate let fileMap: MediaBoxFileMap
|
||||||
|
private var dataRequests = Bag<MediaBoxPartialFileDataRequest>()
|
||||||
|
private let missingRanges: MediaBoxFileMissingRanges
|
||||||
|
private let rangeStatusRequests = Bag<((IndexSet) -> Void, () -> Void)>()
|
||||||
|
private let statusRequests = Bag<((MediaResourceStatus) -> Void, Int32?)>()
|
||||||
|
|
||||||
|
private let fullRangeRequests = Bag<Disposable>()
|
||||||
|
|
||||||
|
private var currentFetch: (Promise<IndexSet>, Disposable)?
|
||||||
|
private var processedAtLeastOneFetch: Bool = false
|
||||||
|
|
||||||
|
init?(queue: Queue, path: String, completePath: String, completed: @escaping (Int32) -> Void) {
|
||||||
|
assert(queue.isCurrent())
|
||||||
|
if let metadataFd = ManagedFile(queue: queue, path: path + ".meta", mode: .readwrite), let fd = ManagedFile(queue: queue, path: path, mode: .readwrite) {
|
||||||
|
self.queue = queue
|
||||||
|
self.path = path
|
||||||
|
self.completePath = completePath
|
||||||
|
self.completed = completed
|
||||||
|
self.metadataFd = metadataFd
|
||||||
|
self.fd = fd
|
||||||
|
if let fileMap = MediaBoxFileMap(fd: self.metadataFd) {
|
||||||
|
self.fileMap = fileMap
|
||||||
|
} else {
|
||||||
|
self.fileMap = MediaBoxFileMap()
|
||||||
|
}
|
||||||
|
self.missingRanges = MediaBoxFileMissingRanges()
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.currentFetch?.1.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
var storedSize: Int32 {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
return self.fileMap.sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset() {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
self.fileMap.reset()
|
||||||
|
self.fileMap.serialize(to: self.metadataFd)
|
||||||
|
|
||||||
|
for request in self.dataRequests.copyItems() {
|
||||||
|
request.completion(MediaResourceData(path: self.path, offset: Int(request.range.lowerBound), size: 0, complete: false))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let updatedRanges = self.missingRanges.reset(fileMap: self.fileMap) {
|
||||||
|
self.updateRequestRanges(updatedRanges, fetch: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.rangeStatusRequests.isEmpty {
|
||||||
|
let ranges = self.fileMap.ranges
|
||||||
|
for (f, _) in self.rangeStatusRequests.copyItems() {
|
||||||
|
f(ranges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateStatuses()
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveLocalFile(tempPath: String) {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
do {
|
||||||
|
try FileManager.default.moveItem(atPath: tempPath, toPath: self.completePath)
|
||||||
|
|
||||||
|
if let size = fileSize(self.completePath) {
|
||||||
|
unlink(self.path)
|
||||||
|
unlink(self.path + ".meta")
|
||||||
|
|
||||||
|
for completion in self.missingRanges.clear() {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (_, disposable) = self.currentFetch {
|
||||||
|
self.currentFetch = nil
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
for request in self.dataRequests.copyItems() {
|
||||||
|
request.completion(MediaResourceData(path: self.completePath, offset: Int(request.range.lowerBound), size: max(0, size - Int(request.range.lowerBound)), complete: true))
|
||||||
|
}
|
||||||
|
self.dataRequests.removeAll()
|
||||||
|
|
||||||
|
for statusRequest in self.statusRequests.copyItems() {
|
||||||
|
statusRequest.0(.Local)
|
||||||
|
}
|
||||||
|
self.statusRequests.removeAll()
|
||||||
|
|
||||||
|
self.completed(self.fileMap.sum)
|
||||||
|
} else {
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyLocalItem(_ item: MediaResourceDataFetchCopyLocalItem) {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
do {
|
||||||
|
if item.copyTo(url: URL(fileURLWithPath: self.completePath)) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let size = fileSize(self.completePath) {
|
||||||
|
unlink(self.path)
|
||||||
|
unlink(self.path + ".meta")
|
||||||
|
|
||||||
|
for completion in self.missingRanges.clear() {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (_, disposable) = self.currentFetch {
|
||||||
|
self.currentFetch = nil
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
for request in self.dataRequests.copyItems() {
|
||||||
|
request.completion(MediaResourceData(path: self.completePath, offset: Int(request.range.lowerBound), size: max(0, size - Int(request.range.lowerBound)), complete: true))
|
||||||
|
}
|
||||||
|
self.dataRequests.removeAll()
|
||||||
|
|
||||||
|
for statusRequest in self.statusRequests.copyItems() {
|
||||||
|
statusRequest.0(.Local)
|
||||||
|
}
|
||||||
|
self.statusRequests.removeAll()
|
||||||
|
|
||||||
|
self.completed(self.fileMap.sum)
|
||||||
|
} else {
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncate(_ size: Int32) {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
let range: Range<Int32> = size ..< Int32.max
|
||||||
|
|
||||||
|
self.fileMap.truncate(size)
|
||||||
|
self.fileMap.serialize(to: self.metadataFd)
|
||||||
|
|
||||||
|
self.checkDataRequestsAfterFill(range: range)
|
||||||
|
}
|
||||||
|
|
||||||
|
func write(offset: Int32, data: Data, dataRange: Range<Int>) {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
self.fd.seek(position: Int64(offset))
|
||||||
|
let written = data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Int in
|
||||||
|
return self.fd.write(bytes.advanced(by: dataRange.lowerBound), count: dataRange.count)
|
||||||
|
}
|
||||||
|
assert(written == dataRange.count)
|
||||||
|
let range: Range<Int32> = offset ..< (offset + Int32(dataRange.count))
|
||||||
|
self.fileMap.fill(range)
|
||||||
|
self.fileMap.serialize(to: self.metadataFd)
|
||||||
|
|
||||||
|
self.checkDataRequestsAfterFill(range: range)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDataRequestsAfterFill(range: Range<Int32>) {
|
||||||
|
var removeIndices: [(Int, MediaBoxPartialFileDataRequest)] = []
|
||||||
|
for (index, request) in self.dataRequests.copyItemsWithIndices() {
|
||||||
|
if request.range.overlaps(range) {
|
||||||
|
var maxValue = request.range.upperBound
|
||||||
|
if let truncationSize = self.fileMap.truncationSize {
|
||||||
|
maxValue = truncationSize
|
||||||
|
}
|
||||||
|
if request.range.lowerBound > maxValue {
|
||||||
|
assertionFailure()
|
||||||
|
removeIndices.append((index, request))
|
||||||
|
} else {
|
||||||
|
let intRange = Range(Int(request.range.lowerBound) ..< Int(maxValue))
|
||||||
|
if self.fileMap.ranges.contains(integersIn: intRange) {
|
||||||
|
removeIndices.append((index, request))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !removeIndices.isEmpty {
|
||||||
|
for (index, request) in removeIndices {
|
||||||
|
self.dataRequests.remove(index)
|
||||||
|
var maxValue = request.range.upperBound
|
||||||
|
if let truncationSize = self.fileMap.truncationSize {
|
||||||
|
maxValue = truncationSize
|
||||||
|
}
|
||||||
|
request.completion(MediaResourceData(path: self.path, offset: Int(request.range.lowerBound), size: Int(maxValue) - Int(request.range.lowerBound), complete: true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isCompleted = false
|
||||||
|
if let truncationSize = self.fileMap.truncationSize, self.fileMap.contains(0 ..< truncationSize) {
|
||||||
|
isCompleted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCompleted {
|
||||||
|
for completion in self.missingRanges.clear() {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let (updatedRanges, completions) = self.missingRanges.fill(range) {
|
||||||
|
self.updateRequestRanges(updatedRanges, fetch: nil)
|
||||||
|
completions.forEach({ $0() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.rangeStatusRequests.isEmpty {
|
||||||
|
let ranges = self.fileMap.ranges
|
||||||
|
for (f, completed) in self.rangeStatusRequests.copyItems() {
|
||||||
|
f(ranges)
|
||||||
|
if isCompleted {
|
||||||
|
completed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isCompleted {
|
||||||
|
self.rangeStatusRequests.removeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateStatuses()
|
||||||
|
|
||||||
|
if isCompleted {
|
||||||
|
for statusRequest in self.statusRequests.copyItems() {
|
||||||
|
statusRequest.0(.Local)
|
||||||
|
}
|
||||||
|
self.statusRequests.removeAll()
|
||||||
|
self.fd.sync()
|
||||||
|
let linkResult = link(self.path, self.completePath)
|
||||||
|
assert(linkResult == 0)
|
||||||
|
self.completed(self.fileMap.sum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read(range: Range<Int32>) -> Data? {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
if self.fileMap.contains(range) {
|
||||||
|
self.fd.seek(position: Int64(range.lowerBound))
|
||||||
|
var data = Data(count: range.count)
|
||||||
|
let readBytes = data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Int in
|
||||||
|
return self.fd.read(bytes, data.count)
|
||||||
|
}
|
||||||
|
if readBytes == data.count {
|
||||||
|
return data
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func data(range: Range<Int32>, waitUntilAfterInitialFetch: Bool, next: @escaping (MediaResourceData) -> Void) -> Disposable {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
if self.fileMap.contains(range) {
|
||||||
|
next(MediaResourceData(path: self.path, offset: Int(range.lowerBound), size: range.count, complete: true))
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|
||||||
|
var waitingUntilAfterInitialFetch = false
|
||||||
|
if waitUntilAfterInitialFetch && !self.processedAtLeastOneFetch {
|
||||||
|
waitingUntilAfterInitialFetch = true
|
||||||
|
} else {
|
||||||
|
next(MediaResourceData(path: self.path, offset: Int(range.lowerBound), size: 0, complete: false))
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = self.dataRequests.add(MediaBoxPartialFileDataRequest(range: range, waitingUntilAfterInitialFetch: waitingUntilAfterInitialFetch, completion: { data in
|
||||||
|
next(data)
|
||||||
|
}))
|
||||||
|
|
||||||
|
let queue = self.queue
|
||||||
|
return ActionDisposable { [weak self] in
|
||||||
|
queue.async {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.dataRequests.remove(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetched(range: Range<Int32>, fetch: @escaping (Signal<IndexSet, NoError>) -> Signal<MediaResourceDataFetchResult, NoError>, completed: @escaping () -> Void) -> Disposable {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
if self.fileMap.contains(range) {
|
||||||
|
completed()
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|
||||||
|
let (index, updatedRanges) = self.missingRanges.addRequest(fileMap: self.fileMap, range: range, completion: {
|
||||||
|
completed()
|
||||||
|
})
|
||||||
|
if let updatedRanges = updatedRanges {
|
||||||
|
self.updateRequestRanges(updatedRanges, fetch: fetch)
|
||||||
|
}
|
||||||
|
|
||||||
|
let queue = self.queue
|
||||||
|
return ActionDisposable { [weak self] in
|
||||||
|
queue.async {
|
||||||
|
if let strongSelf = self {
|
||||||
|
if let updatedRanges = strongSelf.missingRanges.removeRequest(fileMap: strongSelf.fileMap, index: index) {
|
||||||
|
strongSelf.updateRequestRanges(updatedRanges, fetch: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchedFullRange(fetch: @escaping (Signal<IndexSet, NoError>) -> Signal<MediaResourceDataFetchResult, NoError>, completed: @escaping () -> Void) -> Disposable {
|
||||||
|
let queue = self.queue
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
let index = self.fullRangeRequests.add(disposable)
|
||||||
|
self.updateStatuses()
|
||||||
|
|
||||||
|
disposable.set(self.fetched(range: 0 ..< Int32.max, fetch: fetch, completed: { [weak self] in
|
||||||
|
queue.async {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.fullRangeRequests.remove(index)
|
||||||
|
if strongSelf.fullRangeRequests.isEmpty {
|
||||||
|
strongSelf.updateStatuses()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completed()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return ActionDisposable { [weak self] in
|
||||||
|
queue.async {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.fullRangeRequests.remove(index)
|
||||||
|
disposable.dispose()
|
||||||
|
if strongSelf.fullRangeRequests.isEmpty {
|
||||||
|
strongSelf.updateStatuses()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelFullRangeFetches() {
|
||||||
|
self.fullRangeRequests.copyItems().forEach({ $0.dispose() })
|
||||||
|
self.fullRangeRequests.removeAll()
|
||||||
|
|
||||||
|
self.updateStatuses()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateStatuses() {
|
||||||
|
if !self.statusRequests.isEmpty {
|
||||||
|
for (f, size) in self.statusRequests.copyItems() {
|
||||||
|
let status = self.immediateStatus(size: size)
|
||||||
|
f(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rangeStatus(next: @escaping (IndexSet) -> Void, completed: @escaping () -> Void) -> Disposable {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
next(self.fileMap.ranges)
|
||||||
|
if let truncationSize = self.fileMap.truncationSize, self.fileMap.contains(0 ..< truncationSize) {
|
||||||
|
completed()
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = self.rangeStatusRequests.add((next, completed))
|
||||||
|
|
||||||
|
let queue = self.queue
|
||||||
|
return ActionDisposable { [weak self] in
|
||||||
|
queue.async {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.rangeStatusRequests.remove(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func immediateStatus(size: Int32?) -> MediaResourceStatus {
|
||||||
|
let status: MediaResourceStatus
|
||||||
|
if self.fullRangeRequests.isEmpty {
|
||||||
|
status = .Remote
|
||||||
|
} else {
|
||||||
|
let progress: Float
|
||||||
|
if let truncationSize = self.fileMap.truncationSize, truncationSize != 0 {
|
||||||
|
progress = Float(self.fileMap.sum) / Float(truncationSize)
|
||||||
|
} else if let size = size {
|
||||||
|
progress = Float(self.fileMap.sum) / Float(size)
|
||||||
|
} else {
|
||||||
|
progress = 0.0
|
||||||
|
}
|
||||||
|
status = .Fetching(isActive: true, progress: progress)
|
||||||
|
}
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
func status(next: @escaping (MediaResourceStatus) -> Void, completed: @escaping () -> Void, size: Int32?) -> Disposable {
|
||||||
|
let index = self.statusRequests.add((next, size))
|
||||||
|
|
||||||
|
let value = self.immediateStatus(size: size)
|
||||||
|
next(value)
|
||||||
|
if case .Local = value {
|
||||||
|
completed()
|
||||||
|
return EmptyDisposable
|
||||||
|
} else {
|
||||||
|
let queue = self.queue
|
||||||
|
return ActionDisposable { [weak self] in
|
||||||
|
queue.async {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.statusRequests.remove(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateRequestRanges(_ ranges: IndexSet, fetch: ((Signal<IndexSet, NoError>) -> Signal<MediaResourceDataFetchResult, NoError>)?) {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
if ranges.isEmpty {
|
||||||
|
if let (_, disposable) = self.currentFetch {
|
||||||
|
self.currentFetch = nil
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let (promise, _) = self.currentFetch {
|
||||||
|
promise.set(.single(ranges))
|
||||||
|
} else if let fetch = fetch {
|
||||||
|
let promise = Promise<IndexSet>()
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
self.currentFetch = (promise, disposable)
|
||||||
|
disposable.set((fetch(promise.get()) |> deliverOn(self.queue)).start(next: { [weak self] data in
|
||||||
|
if let strongSelf = self {
|
||||||
|
switch data {
|
||||||
|
case .reset:
|
||||||
|
if !strongSelf.fileMap.ranges.isEmpty {
|
||||||
|
strongSelf.reset()
|
||||||
|
}
|
||||||
|
case let .resourceSizeUpdated(size):
|
||||||
|
strongSelf.truncate(Int32(size))
|
||||||
|
case let .dataPart(resourceOffset, data, range, complete):
|
||||||
|
if !data.isEmpty {
|
||||||
|
strongSelf.write(offset: Int32(resourceOffset), data: data, dataRange: range)
|
||||||
|
}
|
||||||
|
if complete {
|
||||||
|
if let maxOffset = strongSelf.fileMap.ranges.max() {
|
||||||
|
let maxValue = max(resourceOffset + range.count, maxOffset)
|
||||||
|
strongSelf.truncate(Int32(maxValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .replaceHeader(data, range):
|
||||||
|
strongSelf.write(offset: 0, data: data, dataRange: range)
|
||||||
|
case let .moveLocalFile(path):
|
||||||
|
strongSelf.moveLocalFile(tempPath: path)
|
||||||
|
case let .copyLocalItem(item):
|
||||||
|
strongSelf.copyLocalItem(item)
|
||||||
|
}
|
||||||
|
if !strongSelf.processedAtLeastOneFetch {
|
||||||
|
strongSelf.processedAtLeastOneFetch = true
|
||||||
|
for request in strongSelf.dataRequests.copyItems() {
|
||||||
|
if request.waitingUntilAfterInitialFetch {
|
||||||
|
request.waitingUntilAfterInitialFetch = false
|
||||||
|
|
||||||
|
if strongSelf.fileMap.contains(request.range) {
|
||||||
|
request.completion(MediaResourceData(path: strongSelf.path, offset: Int(request.range.lowerBound), size: request.range.count, complete: true))
|
||||||
|
} else {
|
||||||
|
request.completion(MediaResourceData(path: strongSelf.path, offset: Int(request.range.lowerBound), size: 0, complete: false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
promise.set(.single(ranges))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class MediaBoxFileMissingRange {
|
||||||
|
var range: Range<Int32>
|
||||||
|
var remainingRanges: IndexSet
|
||||||
|
let completion: () -> Void
|
||||||
|
|
||||||
|
init(range: Range<Int32>, completion: @escaping () -> Void) {
|
||||||
|
self.range = range
|
||||||
|
let intRange = Range(Int(range.lowerBound) ..< Int(range.upperBound))
|
||||||
|
self.remainingRanges = IndexSet(integersIn: intRange)
|
||||||
|
self.completion = completion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class MediaBoxFileMissingRanges {
|
||||||
|
private var requestedRanges = Bag<MediaBoxFileMissingRange>()
|
||||||
|
|
||||||
|
private var missingRanges = IndexSet()
|
||||||
|
|
||||||
|
func clear() -> [() -> Void] {
|
||||||
|
let completions = self.requestedRanges.copyItems().map({ $0.completion })
|
||||||
|
self.requestedRanges.removeAll()
|
||||||
|
return completions
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset(fileMap: MediaBoxFileMap) -> IndexSet? {
|
||||||
|
return self.update(fileMap: fileMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fill(_ range: Range<Int32>) -> (IndexSet, [() -> Void])? {
|
||||||
|
let intRange = Range(Int(range.lowerBound) ..< Int(range.upperBound))
|
||||||
|
if self.missingRanges.intersects(integersIn: intRange) {
|
||||||
|
self.missingRanges.remove(integersIn: intRange)
|
||||||
|
var completions: [() -> Void] = []
|
||||||
|
for (index, item) in self.requestedRanges.copyItemsWithIndices() {
|
||||||
|
if item.range.overlaps(range) {
|
||||||
|
item.remainingRanges.remove(integersIn: intRange)
|
||||||
|
if item.remainingRanges.isEmpty {
|
||||||
|
self.requestedRanges.remove(index)
|
||||||
|
completions.append(item.completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (self.missingRanges, completions)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRequest(fileMap: MediaBoxFileMap, range: Range<Int32>, completion: @escaping () -> Void) -> (Int, IndexSet?) {
|
||||||
|
let index = self.requestedRanges.add(MediaBoxFileMissingRange(range: range, completion: completion))
|
||||||
|
|
||||||
|
return (index, self.update(fileMap: fileMap))
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeRequest(fileMap: MediaBoxFileMap, index: Int) -> IndexSet? {
|
||||||
|
self.requestedRanges.remove(index)
|
||||||
|
return self.update(fileMap: fileMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func update(fileMap: MediaBoxFileMap) -> IndexSet? {
|
||||||
|
var requested = IndexSet()
|
||||||
|
for (item) in self.requestedRanges.copyItems() {
|
||||||
|
let intRange = Range(Int(item.range.lowerBound) ..< Int(item.range.upperBound))
|
||||||
|
requested.insert(integersIn: intRange)
|
||||||
|
}
|
||||||
|
requested.subtract(fileMap.ranges)
|
||||||
|
if requested != self.missingRanges {
|
||||||
|
self.missingRanges = requested
|
||||||
|
return requested
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum MediaBoxFileContent {
|
||||||
|
case complete(String, Int)
|
||||||
|
case partial(MediaBoxPartialFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class MediaBoxFileContext {
|
||||||
|
private let queue: Queue
|
||||||
|
private let path: String
|
||||||
|
private let partialPath: String
|
||||||
|
|
||||||
|
private var content: MediaBoxFileContent
|
||||||
|
|
||||||
|
init?(queue: Queue, path: String, partialPath: String) {
|
||||||
|
assert(queue.isCurrent())
|
||||||
|
|
||||||
|
self.queue = queue
|
||||||
|
self.path = path
|
||||||
|
self.partialPath = partialPath
|
||||||
|
|
||||||
|
var completeImpl: ((Int32) -> Void)?
|
||||||
|
if let size = fileSize(path) {
|
||||||
|
self.content = .complete(path, size)
|
||||||
|
} else if let file = MediaBoxPartialFile(queue: queue, path: partialPath, completePath: path, completed: { size in
|
||||||
|
completeImpl?(size)
|
||||||
|
}) {
|
||||||
|
self.content = .partial(file)
|
||||||
|
completeImpl = { [weak self] size in
|
||||||
|
queue.async {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.content = .complete(path, Int(size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
}
|
||||||
|
|
||||||
|
func data(range: Range<Int32>, waitUntilAfterInitialFetch: Bool, next: @escaping (MediaResourceData) -> Void) -> Disposable {
|
||||||
|
switch self.content {
|
||||||
|
case let .complete(path, size):
|
||||||
|
next(MediaResourceData(path: path, offset: Int(range.lowerBound), size: min(Int(range.upperBound), size), complete: true))
|
||||||
|
return EmptyDisposable
|
||||||
|
case let .partial(file):
|
||||||
|
return file.data(range: range, waitUntilAfterInitialFetch: waitUntilAfterInitialFetch, next: next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetched(range: Range<Int32>, fetch: @escaping (Signal<IndexSet, NoError>) -> Signal<MediaResourceDataFetchResult, NoError>, completed: @escaping () -> Void) -> Disposable {
|
||||||
|
switch self.content {
|
||||||
|
case .complete:
|
||||||
|
return EmptyDisposable
|
||||||
|
case let .partial(file):
|
||||||
|
return file.fetched(range: range, fetch: fetch, completed: completed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchedFullRange(fetch: @escaping (Signal<IndexSet, NoError>) -> Signal<MediaResourceDataFetchResult, NoError>, completed: @escaping () -> Void) -> Disposable {
|
||||||
|
switch self.content {
|
||||||
|
case .complete:
|
||||||
|
return EmptyDisposable
|
||||||
|
case let .partial(file):
|
||||||
|
return file.fetchedFullRange(fetch: fetch, completed: completed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelFullRangeFetches() {
|
||||||
|
switch self.content {
|
||||||
|
case .complete:
|
||||||
|
break
|
||||||
|
case let .partial(file):
|
||||||
|
file.cancelFullRangeFetches()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rangeStatus(next: @escaping (IndexSet) -> Void, completed: @escaping () -> Void) -> Disposable {
|
||||||
|
switch self.content {
|
||||||
|
case let .complete(_, size):
|
||||||
|
next(IndexSet(0 ..< size))
|
||||||
|
completed()
|
||||||
|
return EmptyDisposable
|
||||||
|
case let .partial(file):
|
||||||
|
return file.rangeStatus(next: next, completed: completed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func status(next: @escaping (MediaResourceStatus) -> Void, completed: @escaping () -> Void, size: Int32?) -> Disposable {
|
||||||
|
switch self.content {
|
||||||
|
case .complete:
|
||||||
|
next(.Local)
|
||||||
|
return EmptyDisposable
|
||||||
|
case let .partial(file):
|
||||||
|
return file.status(next: next, completed: completed, size: size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -651,12 +651,24 @@ public final class StoreMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func withUpdatedFlags(_ flags: StoreMessageFlags) -> StoreMessage {
|
||||||
|
if flags == self.flags {
|
||||||
|
return self
|
||||||
|
} else {
|
||||||
|
return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func withUpdatedAttributes(_ attributes: [MessageAttribute]) -> StoreMessage {
|
public func withUpdatedAttributes(_ attributes: [MessageAttribute]) -> StoreMessage {
|
||||||
return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media)
|
return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedLocalTags(_ localTags: LocalMessageTags) -> StoreMessage {
|
public func withUpdatedLocalTags(_ localTags: LocalMessageTags) -> StoreMessage {
|
||||||
return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: self.attributes, media: self.media)
|
if localTags == self.localTags {
|
||||||
|
return self
|
||||||
|
} else {
|
||||||
|
return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: self.attributes, media: self.media)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ public enum AdditionalMessageHistoryViewData {
|
|||||||
case cachedPeerData(PeerId)
|
case cachedPeerData(PeerId)
|
||||||
case cachedPeerDataMessages(PeerId)
|
case cachedPeerDataMessages(PeerId)
|
||||||
case peerChatState(PeerId)
|
case peerChatState(PeerId)
|
||||||
|
case peerGroupState(PeerGroupId)
|
||||||
case totalUnreadCount
|
case totalUnreadCount
|
||||||
case peerNotificationSettings(PeerId)
|
case peerNotificationSettings(PeerId)
|
||||||
}
|
}
|
||||||
@ -21,6 +22,7 @@ public enum AdditionalMessageHistoryViewDataEntry {
|
|||||||
case cachedPeerData(PeerId, CachedPeerData?)
|
case cachedPeerData(PeerId, CachedPeerData?)
|
||||||
case cachedPeerDataMessages(PeerId, [MessageId: Message]?)
|
case cachedPeerDataMessages(PeerId, [MessageId: Message]?)
|
||||||
case peerChatState(PeerId, PeerChatState?)
|
case peerChatState(PeerId, PeerChatState?)
|
||||||
|
case peerGroupState(PeerGroupId, PeerGroupState?)
|
||||||
case totalUnreadCount(Int32)
|
case totalUnreadCount(Int32)
|
||||||
case peerNotificationSettings(PeerNotificationSettings?)
|
case peerNotificationSettings(PeerNotificationSettings?)
|
||||||
}
|
}
|
||||||
@ -1001,6 +1003,11 @@ final class MutableMessageHistoryView {
|
|||||||
self.additionalDatas[i] = .peerChatState(peerId, postbox.peerChatStateTable.get(peerId) as? PeerChatState)
|
self.additionalDatas[i] = .peerChatState(peerId, postbox.peerChatStateTable.get(peerId) as? PeerChatState)
|
||||||
hasChanges = true
|
hasChanges = true
|
||||||
}
|
}
|
||||||
|
case let .peerGroupState(groupId, _):
|
||||||
|
if transaction.currentUpdatedPeerGroupStates.contains(groupId) {
|
||||||
|
self.additionalDatas[i] = .peerGroupState(groupId, postbox.peerGroupStateTable.get(groupId))
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
case .totalUnreadCount:
|
case .totalUnreadCount:
|
||||||
break
|
break
|
||||||
case .peerNotificationSettings:
|
case .peerNotificationSettings:
|
||||||
|
|||||||
35
Postbox/PeerGroupStateView.swift
Normal file
35
Postbox/PeerGroupStateView.swift
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class MutablePeerGroupStateView: MutablePostboxView {
|
||||||
|
let groupId: PeerGroupId
|
||||||
|
var state: PeerGroupState?
|
||||||
|
|
||||||
|
init(postbox: Postbox, groupId: PeerGroupId) {
|
||||||
|
self.groupId = groupId
|
||||||
|
self.state = postbox.peerGroupStateTable.get(groupId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool {
|
||||||
|
if transaction.currentUpdatedPeerGroupStates.contains(self.groupId) {
|
||||||
|
self.state = postbox.peerGroupStateTable.get(self.groupId)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func immutableView() -> PostboxView {
|
||||||
|
return PeerGroupStateView(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class PeerGroupStateView: PostboxView {
|
||||||
|
public let groupId: PeerGroupId
|
||||||
|
public let state: PeerGroupState?
|
||||||
|
|
||||||
|
init(_ view: MutablePeerGroupStateView) {
|
||||||
|
self.groupId = view.groupId
|
||||||
|
self.state = view.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -62,6 +62,16 @@ public final class Modifier {
|
|||||||
self.postbox?.addFeedHoleFromLatestEntries(groupId: groupId)
|
self.postbox?.addFeedHoleFromLatestEntries(groupId: groupId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func addMessagesToGroupFeedIndex(groupId: PeerGroupId, ids: [MessageId]) {
|
||||||
|
assert(!self.disposed)
|
||||||
|
self.postbox?.addMessagesToGroupFeedIndex(groupId: groupId, ids: ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeMessagesFromGroupFeedIndex(groupId: PeerGroupId, ids: [MessageId]) {
|
||||||
|
assert(!self.disposed)
|
||||||
|
self.postbox?.removeMessagesFromGroupFeedIndex(groupId: groupId, ids: ids)
|
||||||
|
}
|
||||||
|
|
||||||
public func replaceChatListHole(groupId: PeerGroupId?, index: MessageIndex, hole: ChatListHole?) {
|
public func replaceChatListHole(groupId: PeerGroupId?, index: MessageIndex, hole: ChatListHole?) {
|
||||||
assert(!self.disposed)
|
assert(!self.disposed)
|
||||||
self.postbox?.replaceChatListHole(groupId: groupId, index: index, hole: hole)
|
self.postbox?.replaceChatListHole(groupId: groupId, index: index, hole: hole)
|
||||||
@ -187,6 +197,16 @@ public final class Modifier {
|
|||||||
self.postbox?.setPeerChatState(id, state: state)
|
self.postbox?.setPeerChatState(id, state: state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getPeerGroupState(_ id: PeerGroupId) -> PeerGroupState? {
|
||||||
|
assert(!self.disposed)
|
||||||
|
return self.postbox?.peerGroupStateTable.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setPeerGroupState(_ id: PeerGroupId, state: PeerGroupState) {
|
||||||
|
assert(!self.disposed)
|
||||||
|
self.postbox?.setPeerGroupState(id, state: state)
|
||||||
|
}
|
||||||
|
|
||||||
public func getPeerChatInterfaceState(_ id: PeerId) -> PeerChatInterfaceState? {
|
public func getPeerChatInterfaceState(_ id: PeerId) -> PeerChatInterfaceState? {
|
||||||
assert(!self.disposed)
|
assert(!self.disposed)
|
||||||
return self.postbox?.peerChatInterfaceStateTable.get(id)
|
return self.postbox?.peerChatInterfaceStateTable.get(id)
|
||||||
@ -835,7 +855,7 @@ public func openPostbox(basePath: String, globalMessageIdsNamespace: MessageId.N
|
|||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
//debugSaveState(basePath: basePath, name: "previous")
|
//debugSaveState(basePath: basePath, name: "previous")
|
||||||
debugRestoreState(basePath: basePath, name: "previous")
|
//debugRestoreState(basePath: basePath, name: "previous")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
loop: while true {
|
loop: while true {
|
||||||
@ -919,6 +939,7 @@ public final class Postbox {
|
|||||||
private var currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]] = [:]
|
private var currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]] = [:]
|
||||||
private var currentItemCollectionInfosOperations: [ItemCollectionInfosOperation] = []
|
private var currentItemCollectionInfosOperations: [ItemCollectionInfosOperation] = []
|
||||||
private var currentUpdatedPeerChatStates = Set<PeerId>()
|
private var currentUpdatedPeerChatStates = Set<PeerId>()
|
||||||
|
private var currentUpdatedPeerGroupStates = Set<PeerGroupId>()
|
||||||
private var currentUpdatedAccessChallengeData: PostboxAccessChallengeData?
|
private var currentUpdatedAccessChallengeData: PostboxAccessChallengeData?
|
||||||
private var currentPendingMessageActionsOperations: [PendingMessageActionsOperation] = []
|
private var currentPendingMessageActionsOperations: [PendingMessageActionsOperation] = []
|
||||||
private var currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32] = [:]
|
private var currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32] = [:]
|
||||||
@ -967,6 +988,7 @@ public final class Postbox {
|
|||||||
let globalMessageHistoryTagsTable: GlobalMessageHistoryTagsTable
|
let globalMessageHistoryTagsTable: GlobalMessageHistoryTagsTable
|
||||||
let localMessageHistoryTagsTable: LocalMessageHistoryTagsTable
|
let localMessageHistoryTagsTable: LocalMessageHistoryTagsTable
|
||||||
let peerChatStateTable: PeerChatStateTable
|
let peerChatStateTable: PeerChatStateTable
|
||||||
|
let peerGroupStateTable: PeerGroupStateTable
|
||||||
let readStateTable: MessageHistoryReadStateTable
|
let readStateTable: MessageHistoryReadStateTable
|
||||||
let synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable
|
let synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable
|
||||||
let contactsTable: ContactTable
|
let contactsTable: ContactTable
|
||||||
@ -1057,6 +1079,7 @@ public final class Postbox {
|
|||||||
self.groupAssociationTable = PeerGroupAssociationTable(valueBox: self.valueBox, table: PeerGroupAssociationTable.tableSpec(49))
|
self.groupAssociationTable = PeerGroupAssociationTable(valueBox: self.valueBox, table: PeerGroupAssociationTable.tableSpec(49))
|
||||||
self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), messageHistoryIndexTable: self.messageHistoryIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, tagsTable: self.messageHistoryTagsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable, groupAssociationTable: self.groupAssociationTable, groupFeedIndexTable: self.groupFeedIndexTable)
|
self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), messageHistoryIndexTable: self.messageHistoryIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, tagsTable: self.messageHistoryTagsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable, groupAssociationTable: self.groupAssociationTable, groupFeedIndexTable: self.groupFeedIndexTable)
|
||||||
self.peerChatStateTable = PeerChatStateTable(valueBox: self.valueBox, table: PeerChatStateTable.tableSpec(13))
|
self.peerChatStateTable = PeerChatStateTable(valueBox: self.valueBox, table: PeerChatStateTable.tableSpec(13))
|
||||||
|
self.peerGroupStateTable = PeerGroupStateTable(valueBox: self.valueBox, table: PeerGroupStateTable.tableSpec(53))
|
||||||
self.peerNameTokenIndexTable = ReverseIndexReferenceTable<PeerIdReverseIndexReference>(valueBox: self.valueBox, table: ReverseIndexReferenceTable<PeerIdReverseIndexReference>.tableSpec(26))
|
self.peerNameTokenIndexTable = ReverseIndexReferenceTable<PeerIdReverseIndexReference>(valueBox: self.valueBox, table: ReverseIndexReferenceTable<PeerIdReverseIndexReference>.tableSpec(26))
|
||||||
self.peerNameIndexTable = PeerNameIndexTable(valueBox: self.valueBox, table: PeerNameIndexTable.tableSpec(27), peerTable: self.peerTable, peerNameTokenIndexTable: self.peerNameTokenIndexTable)
|
self.peerNameIndexTable = PeerNameIndexTable(valueBox: self.valueBox, table: PeerNameIndexTable.tableSpec(27), peerTable: self.peerTable, peerNameTokenIndexTable: self.peerNameTokenIndexTable)
|
||||||
self.contactsTable = ContactTable(valueBox: self.valueBox, table: ContactTable.tableSpec(16), peerNameIndexTable: self.peerNameIndexTable)
|
self.contactsTable = ContactTable(valueBox: self.valueBox, table: ContactTable.tableSpec(16), peerNameIndexTable: self.peerNameIndexTable)
|
||||||
@ -1103,6 +1126,7 @@ public final class Postbox {
|
|||||||
tables.append(self.chatListTable)
|
tables.append(self.chatListTable)
|
||||||
tables.append(self.groupAssociationTable)
|
tables.append(self.groupAssociationTable)
|
||||||
tables.append(self.peerChatStateTable)
|
tables.append(self.peerChatStateTable)
|
||||||
|
tables.append(self.peerGroupStateTable)
|
||||||
tables.append(self.contactsTable)
|
tables.append(self.contactsTable)
|
||||||
tables.append(self.peerRatingTable)
|
tables.append(self.peerRatingTable)
|
||||||
tables.append(self.peerNotificationSettingsTable)
|
tables.append(self.peerNotificationSettingsTable)
|
||||||
@ -1388,6 +1412,24 @@ public final class Postbox {
|
|||||||
self.groupFeedIndexTable.addHoleFromLatestEntries(groupId: groupId, messageHistoryTable: self.messageHistoryTable, operations: &self.currentGroupFeedOperations)
|
self.groupFeedIndexTable.addHoleFromLatestEntries(groupId: groupId, messageHistoryTable: self.messageHistoryTable, operations: &self.currentGroupFeedOperations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate func addMessagesToGroupFeedIndex(groupId: PeerGroupId, ids: [MessageId]) {
|
||||||
|
for id in ids {
|
||||||
|
if let entry = self.messageHistoryIndexTable.get(id), case let .Message(index) = entry {
|
||||||
|
if let message = self.messageHistoryTable.getMessage(index) {
|
||||||
|
self.groupFeedIndexTable.add(groupId: groupId, message: message, operations: &self.currentGroupFeedOperations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func removeMessagesFromGroupFeedIndex(groupId: PeerGroupId, ids: [MessageId]) {
|
||||||
|
for id in ids {
|
||||||
|
if let entry = self.messageHistoryIndexTable.get(id), case let .Message(index) = entry {
|
||||||
|
self.groupFeedIndexTable.remove(groupId: groupId, messageIndex: index, operations: &self.currentGroupFeedOperations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate func replaceChatListHole(groupId: PeerGroupId?, index: MessageIndex, hole: ChatListHole?) {
|
fileprivate func replaceChatListHole(groupId: PeerGroupId?, index: MessageIndex, hole: ChatListHole?) {
|
||||||
self.chatListTable.replaceHole(groupId: groupId, index: index, hole: hole, operations: &self.currentChatListOperations)
|
self.chatListTable.replaceHole(groupId: groupId, index: index, hole: hole, operations: &self.currentChatListOperations)
|
||||||
}
|
}
|
||||||
@ -1670,7 +1712,7 @@ public final class Postbox {
|
|||||||
return self.peerTable.get(peerId)
|
return self.peerTable.get(peerId)
|
||||||
}, updatedTotalUnreadCount: &self.currentUpdatedTotalUnreadCount)
|
}, updatedTotalUnreadCount: &self.currentUpdatedTotalUnreadCount)
|
||||||
|
|
||||||
let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentOperationsByPeerId: self.currentOperationsByPeerId, currentGroupFeedOperations: self.currentGroupFeedOperations, peerIdsWithFilledHoles: self.currentFilledHolesByPeerId, removedHolesByPeerId: self.currentRemovedHolesByPeerId, groupFeedIdsWithFilledHoles: self.currentGroupFeedIdsWithFilledHoles, removedHolesByPeerGroupId: self.currentRemovedHolesByPeerGroupId, chatListOperations: self.currentChatListOperations, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadCount: self.currentUpdatedTotalUnreadCount, peerIdsWithUpdatedUnreadCounts: Set(transactionUnreadCountDeltas.keys), peerIdsWithUpdatedCombinedReadStates: peerIdsWithUpdatedCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, updatedAccessChallengeData: self.currentUpdatedAccessChallengeData, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, currentGroupFeedReadStateContext: self.currentGroupFeedReadStateContext, currentInitialPeerGroupIdsBeforeUpdate: self.currentInitialPeerGroupIdsBeforeUpdate, currentUpdatedMasterClientId: currentUpdatedMasterClientId)
|
let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentOperationsByPeerId: self.currentOperationsByPeerId, currentGroupFeedOperations: self.currentGroupFeedOperations, peerIdsWithFilledHoles: self.currentFilledHolesByPeerId, removedHolesByPeerId: self.currentRemovedHolesByPeerId, groupFeedIdsWithFilledHoles: self.currentGroupFeedIdsWithFilledHoles, removedHolesByPeerGroupId: self.currentRemovedHolesByPeerGroupId, chatListOperations: self.currentChatListOperations, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadCount: self.currentUpdatedTotalUnreadCount, peerIdsWithUpdatedUnreadCounts: Set(transactionUnreadCountDeltas.keys), peerIdsWithUpdatedCombinedReadStates: peerIdsWithUpdatedCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentUpdatedPeerGroupStates: self.currentUpdatedPeerGroupStates, updatedAccessChallengeData: self.currentUpdatedAccessChallengeData, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, currentGroupFeedReadStateContext: self.currentGroupFeedReadStateContext, currentInitialPeerGroupIdsBeforeUpdate: self.currentInitialPeerGroupIdsBeforeUpdate, currentUpdatedMasterClientId: currentUpdatedMasterClientId)
|
||||||
var updatedTransactionState: Int64?
|
var updatedTransactionState: Int64?
|
||||||
var updatedMasterClientId: Int64?
|
var updatedMasterClientId: Int64?
|
||||||
if !transaction.isEmpty {
|
if !transaction.isEmpty {
|
||||||
@ -1714,6 +1756,7 @@ public final class Postbox {
|
|||||||
self.currentItemCollectionItemsOperations.removeAll()
|
self.currentItemCollectionItemsOperations.removeAll()
|
||||||
self.currentItemCollectionInfosOperations.removeAll()
|
self.currentItemCollectionInfosOperations.removeAll()
|
||||||
self.currentUpdatedPeerChatStates.removeAll()
|
self.currentUpdatedPeerChatStates.removeAll()
|
||||||
|
self.currentUpdatedPeerGroupStates.removeAll()
|
||||||
self.currentUpdatedAccessChallengeData = nil
|
self.currentUpdatedAccessChallengeData = nil
|
||||||
self.currentPendingMessageActionsOperations.removeAll()
|
self.currentPendingMessageActionsOperations.removeAll()
|
||||||
self.currentUpdatedMessageActionsSummaries.removeAll()
|
self.currentUpdatedMessageActionsSummaries.removeAll()
|
||||||
@ -1870,6 +1913,11 @@ public final class Postbox {
|
|||||||
self.currentUpdatedPeerChatStates.insert(id)
|
self.currentUpdatedPeerChatStates.insert(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate func setPeerGroupState(_ id: PeerGroupId, state: PeerGroupState) {
|
||||||
|
self.peerGroupStateTable.set(id, state: state)
|
||||||
|
self.currentUpdatedPeerGroupStates.insert(id)
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate func updatePeerChatInterfaceState(_ id: PeerId, update: (PeerChatInterfaceState?) -> (PeerChatInterfaceState?)) {
|
fileprivate func updatePeerChatInterfaceState(_ id: PeerId, update: (PeerChatInterfaceState?) -> (PeerChatInterfaceState?)) {
|
||||||
let updatedState = update(self.peerChatInterfaceStateTable.get(id))
|
let updatedState = update(self.peerChatInterfaceStateTable.get(id))
|
||||||
let (_, updatedEmbeddedState) = self.peerChatInterfaceStateTable.set(id, state: updatedState)
|
let (_, updatedEmbeddedState) = self.peerChatInterfaceStateTable.set(id, state: updatedState)
|
||||||
@ -2254,6 +2302,8 @@ public final class Postbox {
|
|||||||
additionalDataEntries.append(.cachedPeerDataMessages(peerId, messages))
|
additionalDataEntries.append(.cachedPeerDataMessages(peerId, messages))
|
||||||
case let .peerChatState(peerId):
|
case let .peerChatState(peerId):
|
||||||
additionalDataEntries.append(.peerChatState(peerId, self.peerChatStateTable.get(peerId) as? PeerChatState))
|
additionalDataEntries.append(.peerChatState(peerId, self.peerChatStateTable.get(peerId) as? PeerChatState))
|
||||||
|
case let .peerGroupState(groupId):
|
||||||
|
additionalDataEntries.append(.peerGroupState(groupId, self.peerGroupStateTable.get(groupId)))
|
||||||
case .totalUnreadCount:
|
case .totalUnreadCount:
|
||||||
additionalDataEntries.append(.totalUnreadCount(self.messageHistoryMetadataTable.getChatListTotalUnreadCount()))
|
additionalDataEntries.append(.totalUnreadCount(self.messageHistoryMetadataTable.getChatListTotalUnreadCount()))
|
||||||
case let .peerNotificationSettings(peerId):
|
case let .peerNotificationSettings(peerId):
|
||||||
|
|||||||
@ -3,5 +3,6 @@ module sqlcipher {
|
|||||||
header "sqlcipher/sqlite3ext.h"
|
header "sqlcipher/sqlite3ext.h"
|
||||||
header "sqlcipher/SQLite-Bridging.h"
|
header "sqlcipher/SQLite-Bridging.h"
|
||||||
header "sqlcipher/fts3_tokenizer.h"
|
header "sqlcipher/fts3_tokenizer.h"
|
||||||
|
header "../Crc32.h"
|
||||||
export *
|
export *
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ final class PostboxTransaction {
|
|||||||
let currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]]
|
let currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]]
|
||||||
let currentItemCollectionInfosOperations: [ItemCollectionInfosOperation]
|
let currentItemCollectionInfosOperations: [ItemCollectionInfosOperation]
|
||||||
let currentUpdatedPeerChatStates: Set<PeerId>
|
let currentUpdatedPeerChatStates: Set<PeerId>
|
||||||
|
let currentUpdatedPeerGroupStates: Set<PeerGroupId>
|
||||||
let updatedAccessChallengeData: PostboxAccessChallengeData?
|
let updatedAccessChallengeData: PostboxAccessChallengeData?
|
||||||
let currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation]
|
let currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation]
|
||||||
let currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation]
|
let currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation]
|
||||||
@ -130,6 +131,9 @@ final class PostboxTransaction {
|
|||||||
if !currentUpdatedPeerChatStates.isEmpty {
|
if !currentUpdatedPeerChatStates.isEmpty {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if !currentUpdatedPeerGroupStates.isEmpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if self.updatedAccessChallengeData != nil {
|
if self.updatedAccessChallengeData != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -163,7 +167,7 @@ final class PostboxTransaction {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(currentUpdatedState: PostboxCoding?, currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], currentGroupFeedOperations: [PeerGroupId : [GroupFeedIndexOperation]], peerIdsWithFilledHoles: [PeerId: [MessageIndex: HoleFillDirection]], removedHolesByPeerId: [PeerId: [MessageIndex: HoleFillDirection]], groupFeedIdsWithFilledHoles: [PeerGroupId: [MessageIndex: HoleFillDirection]], removedHolesByPeerGroupId: [PeerGroupId: [MessageIndex: HoleFillDirection]], chatListOperations: [WrappedPeerGroupId: [ChatListOperation]], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?], currentUpdatedTotalUnreadCount: Int32?, peerIdsWithUpdatedUnreadCounts: Set<PeerId>, peerIdsWithUpdatedCombinedReadStates: Set<PeerId>, currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set<PeerId>, updatedAccessChallengeData: PostboxAccessChallengeData?, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set<PeerId>?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set<PeerId>, currentGroupFeedReadStateContext: GroupFeedReadStateUpdateContext, currentInitialPeerGroupIdsBeforeUpdate: [PeerId: WrappedPeerGroupId], currentUpdatedMasterClientId: Int64?) {
|
init(currentUpdatedState: PostboxCoding?, currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], currentGroupFeedOperations: [PeerGroupId : [GroupFeedIndexOperation]], peerIdsWithFilledHoles: [PeerId: [MessageIndex: HoleFillDirection]], removedHolesByPeerId: [PeerId: [MessageIndex: HoleFillDirection]], groupFeedIdsWithFilledHoles: [PeerGroupId: [MessageIndex: HoleFillDirection]], removedHolesByPeerGroupId: [PeerGroupId: [MessageIndex: HoleFillDirection]], chatListOperations: [WrappedPeerGroupId: [ChatListOperation]], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?], currentUpdatedTotalUnreadCount: Int32?, peerIdsWithUpdatedUnreadCounts: Set<PeerId>, peerIdsWithUpdatedCombinedReadStates: Set<PeerId>, currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set<PeerId>, currentUpdatedPeerGroupStates: Set<PeerGroupId>, updatedAccessChallengeData: PostboxAccessChallengeData?, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set<PeerId>?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set<PeerId>, currentGroupFeedReadStateContext: GroupFeedReadStateUpdateContext, currentInitialPeerGroupIdsBeforeUpdate: [PeerId: WrappedPeerGroupId], currentUpdatedMasterClientId: Int64?) {
|
||||||
self.currentUpdatedState = currentUpdatedState
|
self.currentUpdatedState = currentUpdatedState
|
||||||
self.currentOperationsByPeerId = currentOperationsByPeerId
|
self.currentOperationsByPeerId = currentOperationsByPeerId
|
||||||
self.currentGroupFeedOperations = currentGroupFeedOperations
|
self.currentGroupFeedOperations = currentGroupFeedOperations
|
||||||
@ -189,6 +193,7 @@ final class PostboxTransaction {
|
|||||||
self.currentItemCollectionItemsOperations = currentItemCollectionItemsOperations
|
self.currentItemCollectionItemsOperations = currentItemCollectionItemsOperations
|
||||||
self.currentItemCollectionInfosOperations = currentItemCollectionInfosOperations
|
self.currentItemCollectionInfosOperations = currentItemCollectionInfosOperations
|
||||||
self.currentUpdatedPeerChatStates = currentUpdatedPeerChatStates
|
self.currentUpdatedPeerChatStates = currentUpdatedPeerChatStates
|
||||||
|
self.currentUpdatedPeerGroupStates = currentUpdatedPeerGroupStates
|
||||||
self.updatedAccessChallengeData = updatedAccessChallengeData
|
self.updatedAccessChallengeData = updatedAccessChallengeData
|
||||||
self.currentGlobalTagsOperations = currentGlobalTagsOperations
|
self.currentGlobalTagsOperations = currentGlobalTagsOperations
|
||||||
self.currentLocalTagsOperations = currentLocalTagsOperations
|
self.currentLocalTagsOperations = currentLocalTagsOperations
|
||||||
|
|||||||
@ -5,6 +5,7 @@ public enum PostboxViewKey: Hashable {
|
|||||||
case itemCollectionIds(namespaces: [ItemCollectionId.Namespace])
|
case itemCollectionIds(namespaces: [ItemCollectionId.Namespace])
|
||||||
case itemCollectionInfo(id: ItemCollectionId)
|
case itemCollectionInfo(id: ItemCollectionId)
|
||||||
case peerChatState(peerId: PeerId)
|
case peerChatState(peerId: PeerId)
|
||||||
|
case peerGroupState(groupId: PeerGroupId)
|
||||||
case orderedItemList(id: Int32)
|
case orderedItemList(id: Int32)
|
||||||
case accessChallengeData
|
case accessChallengeData
|
||||||
case preferences(keys: Set<ValueBoxKey>)
|
case preferences(keys: Set<ValueBoxKey>)
|
||||||
@ -31,6 +32,8 @@ public enum PostboxViewKey: Hashable {
|
|||||||
return 1
|
return 1
|
||||||
case let .peerChatState(peerId):
|
case let .peerChatState(peerId):
|
||||||
return peerId.hashValue
|
return peerId.hashValue
|
||||||
|
case let .peerGroupState(groupId):
|
||||||
|
return groupId.hashValue
|
||||||
case let .itemCollectionInfo(id):
|
case let .itemCollectionInfo(id):
|
||||||
return id.hashValue
|
return id.hashValue
|
||||||
case let .orderedItemList(id):
|
case let .orderedItemList(id):
|
||||||
@ -96,6 +99,12 @@ public enum PostboxViewKey: Hashable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case let .peerGroupState(groupId):
|
||||||
|
if case .peerGroupState(groupId) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .orderedItemList(id):
|
case let .orderedItemList(id):
|
||||||
if case .orderedItemList(id) = rhs {
|
if case .orderedItemList(id) = rhs {
|
||||||
return true
|
return true
|
||||||
@ -212,6 +221,8 @@ func postboxViewForKey(postbox: Postbox, key: PostboxViewKey) -> MutablePostboxV
|
|||||||
return MutableItemCollectionInfoView(postbox: postbox, id: id)
|
return MutableItemCollectionInfoView(postbox: postbox, id: id)
|
||||||
case let .peerChatState(peerId):
|
case let .peerChatState(peerId):
|
||||||
return MutablePeerChatStateView(postbox: postbox, peerId: peerId)
|
return MutablePeerChatStateView(postbox: postbox, peerId: peerId)
|
||||||
|
case let .peerGroupState(groupId):
|
||||||
|
return MutablePeerGroupStateView(postbox: postbox, groupId: groupId)
|
||||||
case let .orderedItemList(id):
|
case let .orderedItemList(id):
|
||||||
return MutableOrderedItemListView(postbox: postbox, collectionId: id)
|
return MutableOrderedItemListView(postbox: postbox, collectionId: id)
|
||||||
case .accessChallengeData:
|
case .accessChallengeData:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user