<commit_before>//
//  nsurlsession.swift
//  deferred
//
//  Created by <NAME> on 10/02/2016.
//  Copyright © 2016 <NAME>. All rights reserved.
//

import Dispatch
import Foundation

public enum URLSessionError: Error, Equatable
{
  case serverStatus(Int)
  case interruptedDownload(URLError, Data)
  case invalidState
}

public class DeferredURLSessionTask<Value>: TBD<Value>
{
  public private(set) weak var urlSessionTask: URLSessionTask? = nil

  init(qos: DispatchQoS = .current, error: Error)
  {
    super.init(qos: qos) { $0.resolve(error: error) }
  }

  init(qos: DispatchQoS = .current, task: (Resolver<Value>) -> URLSessionTask)
  {
    var t: URLSessionTask?
    super.init(qos: qos, task: { t = task($0) })
    urlSessionTask = t.unsafelyUnwrapped
  }

  deinit {
    urlSessionTask?.cancel()
  }

  @discardableResult
  public override func cancel(_ error: DeferredError) -> Bool
  {
    guard !self.isResolved else { return false }

    // try to propagate the cancellation upstream
    urlSessionTask?.cancel()
    return true
  }

  public override func notify(queue: DispatchQueue? = nil, boostQoS: Bool = true,
                              handler task: @escaping (Result<Value, Error>) -> Void)
  {
    if state == .waiting
    {
      beginExecution()
    }
    super.notify(queue: queue, boostQoS: boostQoS, handler: task)
  }

  public override func beginExecution()
  {
    urlSessionTask?.resume()
    super.beginExecution()
  }
}

private func validateURL(_ request: URLRequest) throws
{
  let scheme = request.url?.scheme ?? "invalid"
  if scheme != "http" && scheme != "https"
  {
    let message = "deferred does not support url scheme \"\(scheme)\""
    throw DeferredError.invalid(message)
  }
}

extension URLSession
{
  private func dataCompletion(_ resolver: Resolver<(Data, HTTPURLResponse)>) -> (Data?, URLResponse?, Error?) -> Void
  {
    return {
      (data: Data?, response: URLResponse?, error: Error?) in

      if let error = error
      {
        resolver.resolve(error: error)
        return
      }

      if let d = data, let r = response as? HTTPURLResponse
      {
        resolver.resolve(value: (d,r))
      }
      else // Probably an impossible situation
      { resolver.resolve(error: URLSessionError.invalidState) }
    }
  }

  public func deferredDataTask(qos: DispatchQoS = .current,
                               with request: URLRequest) -> DeferredURLSessionTask<(Data, HTTPURLResponse)>
  {
    do { try validateURL(request) }
    catch {
      return DeferredURLSessionTask(qos: qos, error: error)
    }

    return DeferredURLSessionTask(qos: qos) {
      dataTask(with: request, completionHandler: dataCompletion($0))
    }
  }

  public func deferredDataTask(qos: DispatchQoS = .current,
                               with url: URL) -> DeferredURLSessionTask<(Data, HTTPURLResponse)>
  {
    return deferredDataTask(qos: qos, with: URLRequest(url: url))
  }

  public func deferredUploadTask(qos: DispatchQoS = .current,
                                 with request: URLRequest, fromData bodyData: Data) -> DeferredURLSessionTask<(Data, HTTPURLResponse)>
  {
    do { try validateURL(request) }
    catch {
      return DeferredURLSessionTask(qos: qos, error: error)
    }

    return DeferredURLSessionTask(qos: qos) {
      uploadTask(with: request, from: bodyData, completionHandler: dataCompletion($0))
    }
  }

  public func deferredUploadTask(qos: DispatchQoS = .current,
                                 with request: URLRequest, fromFile fileURL: URL) -> DeferredURLSessionTask<(Data, HTTPURLResponse)>
  {
    do { try validateURL(request) }
    catch {
      return DeferredURLSessionTask(qos: qos, error: error)
    }

    return DeferredURLSessionTask(qos: qos) {
      uploadTask(with: request, fromFile: fileURL, completionHandler: dataCompletion($0))
    }
  }
}

private class DeferredDownloadTask<Value>: DeferredURLSessionTask<Value>
{
  @discardableResult
  public override func cancel(_ error: DeferredError) -> Bool
  {
    guard !self.isResolved else { return false }

    let task = urlSessionTask as! URLSessionDownloadTask

#if os(Linux) && !compiler(>=5.0)
    // swift-corelibs-foundation calls NSUnimplemented() as the body of cancel(byProducingResumeData:)
    task.cancel()
#else
    // try to propagate the cancellation upstream
    task.cancel(byProducingResumeData: { _ in }) // Let the completion handler collect the data for resuming.
#endif
    return true
  }
}

extension URLSession
{
  private func downloadCompletion(_ resolver: Resolver<(URL, HTTPURLResponse)>) -> (URL?, URLResponse?, Error?) -> Void
  {
    return {
      (location: URL?, response: URLResponse?, error: Error?) in

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
      // rdar://29623544 and https://bugs.swift.org/browse/SR-3403
      let URLSessionDownloadTaskResumeData = NSURLSessionDownloadTaskResumeData
#endif
      if let error = error as? URLError,
         let data = error.userInfo[URLSessionDownloadTaskResumeData] as? Data
      {
        resolver.resolve(error: URLSessionError.interruptedDownload(error, data))
        return<commit_msg>improve handling of weak reference to the `URLSessionTask`<commit_after>//
//  nsurlsession.swift
//  deferred
//
//  Created by <NAME> on 10/02/2016.
//  Copyright © 2016 <NAME>. All rights reserved.
//

import Dispatch
import Foundation

public enum URLSessionError: Error, Equatable
{
  case serverStatus(Int)
  case interruptedDownload(URLError, Data)
  case invalidState
}

public class DeferredURLSessionTask<Value>: TBD<Value>
{
  public private(set) weak var urlSessionTask: URLSessionTask? = nil

  init(qos: DispatchQoS = .current, error: Error)
  {
    super.init(qos: qos) { $0.resolve(error: error) }
  }

  init(qos: DispatchQoS = .current, task: (Resolver<Value>) -> URLSessionTask)
  {
    var resolver: Resolver<Value>!
    super.init(qos: qos, task: { resolver = $0 })
    let task = task(resolver)
    urlSessionTask = task
    resolver.retainSource(task)
  }

  deinit {
    urlSessionTask?.cancel()
  }

  @discardableResult
  public override func cancel(_ error: DeferredError) -> Bool
  {
    guard !self.isResolved else { return false }

    // try to propagate the cancellation upstream
    urlSessionTask?.cancel()
    return true
  }

  public override func notify(queue: DispatchQueue? = nil, boostQoS: Bool = true,
                              handler task: @escaping (Result<Value, Error>) -> Void)
  {
    if state == .waiting
    {
      beginExecution()
    }
    super.notify(queue: queue, boostQoS: boostQoS, handler: task)
  }

  public override func beginExecution()
  {
    urlSessionTask?.resume()
    super.beginExecution()
  }
}

private func validateURL(_ request: URLRequest) throws
{
  let scheme = request.url?.scheme ?? "invalid"
  if scheme != "http" && scheme != "https"
  {
    let message = "deferred does not support url scheme \"\(scheme)\""
    throw DeferredError.invalid(message)
  }
}

extension URLSession
{
  private func dataCompletion(_ resolver: Resolver<(Data, HTTPURLResponse)>) -> (Data?, URLResponse?, Error?) -> Void
  {
    return {
      (data: Data?, response: URLResponse?, error: Error?) in

      if let error = error
      {
        resolver.resolve(error: error)
        return
      }

      if let d = data, let r = response as? HTTPURLResponse
      {
        resolver.resolve(value: (d,r))
      }
      else // Probably an impossible situation
      { resolver.resolve(error: URLSessionError.invalidState) }
    }
  }

  public func deferredDataTask(qos: DispatchQoS = .current,
                               with request: URLRequest) -> DeferredURLSessionTask<(Data, HTTPURLResponse)>
  {
    do { try validateURL(request) }
    catch {
      return DeferredURLSessionTask(qos: qos, error: error)
    }

    return DeferredURLSessionTask(qos: qos) {
      dataTask(with: request, completionHandler: dataCompletion($0))
    }
  }

  public func deferredDataTask(qos: DispatchQoS = .current,
                               with url: URL) -> DeferredURLSessionTask<(Data, HTTPURLResponse)>
  {
    return deferredDataTask(qos: qos, with: URLRequest(url: url))
  }

  public func deferredUploadTask(qos: DispatchQoS = .current,
                                 with request: URLRequest, fromData bodyData: Data) -> DeferredURLSessionTask<(Data, HTTPURLResponse)>
  {
    do { try validateURL(request) }
    catch {
      return DeferredURLSessionTask(qos: qos, error: error)
    }

    return DeferredURLSessionTask(qos: qos) {
      uploadTask(with: request, from: bodyData, completionHandler: dataCompletion($0))
    }
  }

  public func deferredUploadTask(qos: DispatchQoS = .current,
                                 with request: URLRequest, fromFile fileURL: URL) -> DeferredURLSessionTask<(Data, HTTPURLResponse)>
  {
    do { try validateURL(request) }
    catch {
      return DeferredURLSessionTask(qos: qos, error: error)
    }

    return DeferredURLSessionTask(qos: qos) {
      uploadTask(with: request, fromFile: fileURL, completionHandler: dataCompletion($0))
    }
  }
}

private class DeferredDownloadTask<Value>: DeferredURLSessionTask<Value>
{
  @discardableResult
  public override func cancel(_ error: DeferredError) -> Bool
  {
    guard !self.isResolved, let task = urlSessionTask as? URLSessionDownloadTask else { return false }

#if os(Linux) && !compiler(>=5.0)
    // swift-corelibs-foundation calls NSUnimplemented() as the body of cancel(byProducingResumeData:)
    task.cancel()
#else
    // try to propagate the cancellation upstream
    task.cancel(byProducingResumeData: { _ in }) // Let the completion handler collect the data for resuming.
#endif
    return true
  }
}

extension URLSession
{
  private func downloadCompletion(_ resolver: Resolver<(URL, HTTPURLResponse)>) -> (URL?, URLResponse?, Error?) -> Void
  {
    return {
      (location: URL?, response: URLResponse?, error: Error?) in

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
      // rdar://29623544 and https://bugs.swift.org/browse/SR-3403
      let URLSessionDownloadTaskResumeData = NSURLSessionDownloadTaskResumeData
#endif
      if let error = error as? URLError,
         let data = error.userInfo[URLSessionDownloadTaskResumeData] as? Data
      {
        resolver.resolve(error: URLSessionError.interruptedDownload(error, data))
        return
{'fraction_non_alphanumeric': 0.10866005201811, 'fraction_numerical': 0.005972449667662075, 'mean_word_length': 3.129673826571201, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 58, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7228868', 'n_tokens_mistral': 3252, 'n_tokens_neox': 3145, 'n_words': 808}