FileUtils.swift 3.29 KB
Newer Older
Drew's avatar
Drew committed
1 2 3 4 5 6 7 8 9 10 11 12 13
// Copyright (c) 2016 Drew Crawford.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Drew's avatar
Drew committed
14

Drew's avatar
Drew committed
15
#if os(OSX)
Drew's avatar
Drew committed
16
import Darwin
Drew's avatar
Drew committed
17 18 19
#elseif os(Linux)
import Glibc
#endif
Drew's avatar
Drew committed
20

Drew's avatar
Drew committed
21
enum FileUtilsError: Error {
Drew's avatar
Drew committed
22
    case OpenError(Int, String)
Drew's avatar
Drew committed
23 24
    case ReadError(Int)
    case CloseError(Int)
Drew's avatar
Drew committed
25
    case WriteError(Int)
Drew's avatar
Drew committed
26 27 28 29
    case UTF8Error
}

extension String {
Drew's avatar
Drew committed
30
    init(file fileName: String) throws {
Drew's avatar
Drew committed
31 32 33 34
        let file = open(fileName, O_RDONLY)
        var completeFile: [CChar]  = []
        //CANNOT defer cleaning up this resource
        //PAY ATTENTION
Drew's avatar
Drew committed
35
        if file == -1 { throw FileUtilsError.OpenError(Int(errno), fileName) }
Drew's avatar
Drew committed
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
        readLoop: while true {
            var buf = [CChar](repeating: 0, count: 4096)
            let bytesRead = buf.withUnsafeMutableBufferPointer {
                return read(file, $0.baseAddress, $0.count)
            }
            switch(bytesRead) {
                case 0:
                break readLoop //EOF

                case -1:
                close(file) //CLEAN UP open resource
                throw FileUtilsError.ReadError(Int(errno))

                default:
                completeFile.append(contentsOf: buf[0..<bytesRead])
            }
        }
        // holy shit, UB if we don't append a zero here
        // https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20160404/001633.html
        completeFile.append(0)
        guard let str = String(validatingUTF8: completeFile) else {
            close(file)
            throw FileUtilsError.UTF8Error
        }

        self.init(str)
        if close(file) != 0 {
            throw FileUtilsError.CloseError(Int(errno))
        }
    }
Drew's avatar
Drew committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94

    func write(file fileName: String) throws {
        #if os(OSX)
        let os_write = Darwin.write
        #elseif os(Linux)
        let os_write = Glibc.write
        #endif
        let file = open(fileName,O_WRONLY | O_CREAT, 0o600)
        //CANNOT defer cleaning up this resource
        //PAY ATTENTION
        if file == -1 { throw FileUtilsError.OpenError(Int(errno), fileName) }
        let length = self.utf8.count
        let bytesWritten = self.withCString() {(ptr) -> Int in
            return os_write(file, ptr, length)
        }
        if bytesWritten != self.utf8.count {
            close(file) //CLEAN UP open resource
            throw FileUtilsError.WriteError(Int(errno))
        }
        if close(file) != 0 {
            throw FileUtilsError.CloseError(Int(errno))
        }
    }
}

extension String {
    ///The location of the directory for the item.
    var directoryPath: String {
        var position = self.characters.endIndex
95
        for c in self.characters.reversed() {
Drew's avatar
Drew committed
96 97 98
            if c == "/" {
                return String(self.characters[self.characters.startIndex..<position])
            }
99
            position = self.characters.index(before: position)
Drew's avatar
Drew committed
100 101 102
        }
        return self
    }
Drew's avatar
Drew committed
103
}