8
8
9
9
import TSCUtility
10
10
import TSFCAS
11
+ import TSFCASFileTree
12
+
13
+ private extension String {
14
+ func prepending( _ prefix: String ) -> String {
15
+ return prefix + self
16
+ }
17
+ }
11
18
12
19
/// Basic writer implementation that resembles a linked list where each node contains control data (like the channel)
13
20
/// and refs[0] always points to the dataID of the data chunk and refs[1] has the data ID for the next node in the
14
21
/// chain, if it's not the last node. This implementation is not thread safe.
15
- public struct LLBLinkedListStreamWriter : LLBCASStreamWriter {
22
+ public struct LLBLinkedListStreamWriter {
16
23
private let db : LLBCASDatabase
24
+ private let ext : String
17
25
18
26
public private( set) var latestID : LLBFuture < LLBDataID > ?
19
27
20
- public init ( _ db: LLBCASDatabase ) {
28
+ public init ( _ db: LLBCASDatabase , ext : String ? = nil ) {
21
29
self . db = db
30
+ self . ext = ext? . prepending ( " . " ) ?? " "
22
31
}
23
32
24
33
@discardableResult
25
34
public mutating func append( data: LLBByteBuffer , channel: UInt8 , _ ctx: Context ) -> LLBFuture < LLBDataID > {
26
35
let latest = (
27
36
// Append on the previously cached node, or use nil as sentinel if this is the first write.
28
37
latestID? . map { $0 } ?? db. group. next ( ) . makeSucceededFuture ( nil )
29
- ) . flatMap { [ db] ( currentDataID: LLBDataID ? ) -> LLBFuture < LLBDataID > in
30
- db. put ( data: data, ctx) . flatMap { [ db] contentID in
31
- // Put the channel as the only byte in the control node.
32
- let controlData = LLBByteBuffer ( bytes: [ channel] )
33
- // compactmap to remove the currentDataID in case it was the nil sentinel.
34
- let refs : [ LLBDataID ] = [ contentID, currentDataID] . compactMap { $0 }
35
- return db. put ( refs: refs, data: controlData, ctx)
38
+ ) . flatMap { [ db, ext] ( currentDataID: LLBDataID ? ) -> LLBFuture < LLBDataID > in
39
+ db. put ( data: data, ctx) . flatMap { [ db, ext] contentID in
40
+
41
+ var entries = [
42
+ LLBDirectoryEntryID (
43
+ info: . init( name: " \( channel) \( ext) " , type: . plainFile, size: data. readableBytes) ,
44
+ id: contentID
45
+ ) ,
46
+ ]
47
+
48
+ if let currentDataID = currentDataID {
49
+ entries. append (
50
+ LLBDirectoryEntryID (
51
+ info: . init( name: " prev " , type: . directory, size: 0 ) ,
52
+ id: currentDataID
53
+ )
54
+ )
55
+ }
56
+ return LLBCASFileTree . create ( files: entries, in: db, ctx) . map { $0. id }
36
57
}
37
58
}
38
59
@@ -41,77 +62,10 @@ public struct LLBLinkedListStreamWriter: LLBCASStreamWriter {
41
62
}
42
63
}
43
64
44
- public struct LLBLinkedListStreamReader : LLBCASStreamReader {
45
- private let db : LLBCASDatabase
46
-
47
- public init ( _ db: LLBCASDatabase ) {
48
- self . db = db
49
- }
50
-
51
- public func read(
52
- id: LLBDataID ,
53
- channels: [ UInt8 ] ? ,
54
- lastReadID: LLBDataID ? ,
55
- _ ctx: Context ,
56
- readerBlock: @escaping ( UInt8 , LLBByteBuffer ) throws -> Bool
57
- ) -> LLBFuture < Void > {
58
- return innerRead (
59
- id: id,
60
- channels: channels,
61
- lastReadID: lastReadID,
62
- ctx,
63
- readerBlock: readerBlock
64
- ) . map { _ in ( ) }
65
- }
66
-
67
- private func innerRead(
68
- id: LLBDataID ,
69
- channels: [ UInt8 ] ? = nil ,
70
- lastReadID: LLBDataID ? = nil ,
71
- _ ctx: Context ,
72
- readerBlock: @escaping ( UInt8 , LLBByteBuffer ) throws -> Bool
73
- ) -> LLBFuture < Bool > {
74
- if id == lastReadID {
75
- return db. group. next ( ) . makeSucceededFuture ( true )
76
- }
77
-
78
- return db. get ( id, ctx) . flatMap { [ db] node -> LLBFuture < Bool > in
79
- guard let node = node, let channel = node. data. getBytes ( at: 0 , length: 1 ) ? . first else {
80
- return db. group. next ( ) . makeFailedFuture ( LLBCASStreamError . invalid)
81
- }
82
-
83
- let readChainFuture : LLBFuture < Bool >
84
- // If there are 2 refs, it's an intermediate node in the linked list, so we schedule a read of the next
85
- // node before scheduling a read of the current node.
86
- if node. refs. count == 2 {
87
- readChainFuture = self . innerRead (
88
- id: node. refs [ 1 ] ,
89
- channels: channels,
90
- lastReadID: lastReadID,
91
- ctx,
92
- readerBlock: readerBlock
93
- )
94
- } else {
95
- // If this is the last node, schedule a sentinel read that returns to keep on reading.
96
- readChainFuture = db. group. next ( ) . makeSucceededFuture ( true )
97
- }
98
-
99
- return readChainFuture. flatMap { [ db] shouldContinue -> LLBFuture < Bool > in
100
- // If we don't want to continue reading, or if the channel is not requested, close the current chain
101
- // and propagate the desire to keep on reading.
102
- guard shouldContinue && channels? . contains ( channel) ?? true else {
103
- return db. group. next ( ) . makeSucceededFuture ( shouldContinue)
104
- }
105
-
106
- // Read the node since it's expected to exist.
107
- return db. get ( node. refs [ 0 ] , ctx) . flatMapThrowing { ( dataNode: LLBCASObject ? ) -> Bool in
108
- guard let dataNode = dataNode else {
109
- throw LLBCASStreamError . missing
110
- }
111
-
112
- return try readerBlock ( channel, dataNode. data)
113
- }
114
- }
115
- }
65
+ public extension LLBLinkedListStreamWriter {
66
+ @discardableResult
67
+ @inlinable
68
+ mutating func append( data: LLBByteBuffer , _ ctx: Context ) -> LLBFuture < LLBDataID > {
69
+ return append ( data: data, channel: 0 , ctx)
116
70
}
117
71
}
0 commit comments