Repository: vapor/vapor
File: Sources/Vapor/Utilities/FileIO.swift (Swift)

Code before:
    ///
    /// - parameters:
    ///     - path: Path to file on the disk.
    ///     - buffer: The `ByteBuffer` to write.
    /// - returns: `Future` that will complete when the file write is finished.
    public func writeFile(_ buffer: ByteBuffer, at path: String) -> EventLoopFuture<Void> {
        do {
            let fd = try NIOFileHandle(path: path, mode: .write, flags: .allowFileCreation())
            let done = io.write(fileHandle: fd, buffer: buffer, eventLoop: self.request.eventLoop)
            done.whenComplete { _ in
                try? fd.close()
            }

            return done
        } catch {
            return self.request.eventLoop.makeFailedFuture(error)
        }
    }

    /// Generates a fresh ETag for a file or returns its currently cached one.
    /// - Parameters:
    ///   - path: The file's path.
    ///   - lastModified: When the file was last modified.
    /// - Returns: An `EventLoopFuture<String>` which holds the ETag.
    private func generateETagHash(path: String, lastModified: Date) -> EventLoopFuture<String> {
        return NIOLock().withLock {
            var hashingDictionary = request.application.storage[FileMiddleware.ETagHashes.self] ?? [:]

            if let hash = hashingDictionary[path], hash.lastModified == lastModified {
                return request.eventLoop.makeSucceededFuture(hash.digest.hex)
            } else {
                return collectFile(at: path).map { buffer in
                    let digest = SHA256.hash(data: buffer.readableBytesView)

                    // update hash in dictionary
                    hashingDictionary[path] = FileMiddleware.ETagHashes.FileHash(lastModified: lastModified, digest: digest)
                    request.application.storage[FileMiddleware.ETagHashes.self] = hashingDictionary

                    return digest.hex
                }
            }
        }
    }
}

extension HTTPHeaders.Range.Value {
    
    fileprivate func asByteBufferBounds(withMaxSize size: Int, logger: Logger) throws -> (offset: Int64, byteCount: Int) {
        switch self {
            case .start(let value):
                guard value <= size, value >= 0 else {

Review comment (suggestion):
We should have a central lock on `FileIO` rather than recreating one every time

Code after:
            }
        }
    }
    
    /// Write the contents of buffer to a file at the supplied path.
    ///
    ///     let data = ByteBuffer(string: "ByteBuffer")
    ///     try req.fileio.writeFile(data, at: "/path/to/file.txt").wait()
    ///
    /// - parameters:
    ///     - path: Path to file on the disk.
    ///     - buffer: The `ByteBuffer` to write.
    /// - returns: `Future` that will complete when the file write is finished.
    public func writeFile(_ buffer: ByteBuffer, at path: String) -> EventLoopFuture<Void> {
        self.request.eventLoop.flatSubmit {
            do {
                let fd = try NIOFileHandle(path: path, mode: .write, flags: .allowFileCreation())
                let fdWrapper = NIOLoopBound(fd, eventLoop: self.request.eventLoop)
                let done = io.write(fileHandle: fd, buffer: buffer, eventLoop: self.request.eventLoop)
                done.whenComplete { _ in
                    try? fdWrapper.value.close()
                }
                return done
            } catch {
                return self.request.eventLoop.makeFailedFuture(error)
            }
        }
    }

    /// Generates a fresh ETag for a file or returns its currently cached one.
    /// - Parameters:
    ///   - path: The file's path.
    ///   - lastModified: When the file was last modified.
    /// - Returns: An `EventLoopFuture<String>` which holds the ETag.
    private func generateETagHash(path: String, lastModified: Date) -> EventLoopFuture<String> {
        if let hash = request.application.storage[FileMiddleware.ETagHashes.self]?[path], hash.lastModified == lastModified {
            return request.eventLoop.makeSucceededFuture(hash.digestHex)
        } else {
            return collectFile(at: path).map { buffer in
                let digest = SHA256.hash(data: buffer.readableBytesView)
                
                // update hash in dictionary
                request.application.storage[FileMiddleware.ETagHashes.self]?[path] = FileMiddleware.ETagHashes.FileHash(lastModified: lastModified, digestHex: digest.hex)

                return digest.hex
            }
        }
    }
}

extension HTTPHeaders.Range.Value {