summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/Logging/TextFileStream.swift
blob: de1aad6d917fcee22569e175853c4d7cbf8477e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//
//  TextFileStream.swift
//  MullvadVPN
//
//  Created by pronebird on 05/08/2020.
//  Copyright © 2020 Mullvad VPN AB. All rights reserved.
//

#if DEBUG

import Foundation
import Darwin

class TextFileStream<Codec> where Codec: UnicodeCodec {
    let fileDescriptor: Int32

    private let readSource: DispatchSourceRead
    private let queue = DispatchQueue(label: "net.mullvad.MullvadVPN.TextFileStream<\(Codec.self)>")
    private let stringStream: StringStreamIterator<Codec>

    init?(fileURL: URL, separator: Character) {
        let filePath = fileURL.path.utf8CString.map { $0 }

        let fileDescriptor = open(filePath, O_RDONLY)
        if (fileDescriptor == -1) {
            return nil
        }

        // Avoid blocking the read operation
        _ = fcntl(fileDescriptor, F_SETFL, O_NONBLOCK);

        let readSource = DispatchSource.makeReadSource(fileDescriptor: fileDescriptor, queue: queue)
        readSource.setCancelHandler {
            close(fileDescriptor)
        }

        stringStream = StringStreamIterator(separator: separator)

        self.readSource = readSource
        self.fileDescriptor = fileDescriptor
    }

    deinit {
        readSource.cancel()
    }

    func read(_ handler: @escaping (String) -> Void) {
        readSource.setEventHandler { [weak self] in
            guard let self = self else { return }

            let estimated = Int(self.readSource.data + 1)
            var buffer = [Codec.CodeUnit](repeating: 0, count: estimated)
            let actual = Darwin.read(self.fileDescriptor, &buffer, estimated)

            if actual == -1 {
                print("TextFileStream<\(Codec.self)>: read error: \(errno)")
            }

            if actual > 0 {
                let bytes = buffer[..<actual]
                self.stringStream.append(bytes: bytes)

                while let s = self.stringStream.next() {
                    handler(s)
                }
            }
        }
        readSource.activate()
    }

    func cancel() {
        readSource.cancel()
    }

}

#endif