Repository: expo/expo
File: packages/expo-video/ios/Cache/CachedResource.swift (Swift)

Code before:
    if let contentType {
      request.contentInformationRequest?.contentType = contentType
      request.contentInformationRequest?.contentLength = mediaInfo.expectedContentLength
      request.contentInformationRequest?.isByteRangeAccessSupported = mediaInfo.supportsByteRangeAccess
      request.response = fakeResponse
    }
  }

  func writeData(data: Data, offset: Int64) async {
    guard !data.isEmpty else {
      log.warn("Attempted to write empty data at offset \(offset), skipping")
      return
    }

    guard offset >= 0 else {
      log.error("Invalid negative offset: \(offset)")
      return
    }

    do {
      try await writeCoordinator.performWrite(
        data: data,
        offset: offset,
        fileHandle: fileHandle,
        mediaInfo: mediaInfo
      )
    } catch {
      log.warn("Failed to write at offset \(offset) with the file handle: \(error)")
    }
  }

  // Returns data for the requested range if fully cached
  // Expects 'from' and 'to' as inclusive byte positions
  func requestData(from: Int64, to: Int64) -> Data? {
    guard from <= to else {
      log.error("Invalid range: from=\(from) > to=\(to)")
      return nil
    }

    if canRespondWithData(from: from, to: to) {
      let length64 = to - from + 1

      guard length64 >= 0 && length64 <= Int.max else {
        log.error("Requested range length \(length64) exceeds Int.max")
        return nil
      }

      let length = Int(length64)
      return fileHandle.readData(withOffset: from, forLength: length)
    }
    return nil

Review comment (bug):
Just to note: actor's functions **don't** isolate arguments which means that `fileHandle` and `mediaInfo` can still be accessed concurrently (e.g. by `requestData(from:to)`) and lead to data races.

Not sure if it's easy to do right now, but in my opinion the entire `CachedResource` should be an actor if you want to guarantee safe access.

Code after:
    if let contentType {
      request.contentInformationRequest?.contentType = contentType
      request.contentInformationRequest?.contentLength = mediaInfo.expectedContentLength
      request.contentInformationRequest?.isByteRangeAccessSupported = mediaInfo.supportsByteRangeAccess
      request.response = fakeResponse
    }
  }

  func writeData(data: Data, offset: Int64) async {
    guard !data.isEmpty else {
      log.warn("[expo-video] Attempted to write empty data at offset \(offset), skipping")
      return
    }

    guard offset >= 0 else {
      log.error("Invalid negative offset: \(offset)")
      return
    }

    do {
      try await writeCoordinator.performWrite(
        data: data,
        offset: offset,
        fileHandle: fileHandle,
        mediaInfo: mediaInfo
      )
    } catch {
      log.warn("[expo-video] Failed to write at offset \(offset) with the file handle: \(error)")
    }
  }

  // Returns data for the requested range if fully cached
  // Expects 'from' and 'to' as inclusive byte positions
  func requestData(from: Int64, to: Int64) -> Data? {
    guard from <= to else {
      log.error("Invalid range: from=\(from) > to=\(to)")
      return nil
    }

    if canRespondWithData(from: from, to: to) {
      let length64 = to - from + 1

      guard length64 >= 0 && length64 <= Int.max else {
        log.error("Requested range length \(length64) exceeds Int.max")
        return nil
      }

      let length = Int(length64)
      return fileHandle.readData(withOffset: from, forLength: length)
    }
    return nil