<commit_before>//
//  ImageStore.swift
//  ImageStore
//
//  Created by miup on 2017/08/01.
//

import UIKit

public struct ImageStoreConfig {

    let maxDownloadSize: Int64
    let cacheLimit: Int64

    public init(maxDownloadSize: Int64 = Int64(2e8), cacheLimit: Int64 = Int64(200e8)) {
        self.maxDownloadSize = maxDownloadSize
        self.cacheLimit = cacheLimit
    }
}

public final class ImageStore: NSObject {

    public typealias ImageStoreCompletionHandler = ((UIImage?) -> Void)

    private(set) public static var shared: ImageStore = ImageStore(ImageStoreConfig())

    public class func reset(config: ImageStoreConfig = ImageStoreConfig()) {
        shared = ImageStore(config)
    }


    fileprivate let config: ImageStoreConfig
    fileprivate var completionsByURLString: [String: [ImageStoreCompletionHandler]] = [:]
    fileprivate var downloadTaskByURLString: [String: URLSessionDownloadTask] = [:]

    private(set) lazy var cache: NSCache<AnyObject, UIImage> = {
        let cache: NSCache<AnyObject, UIImage> = NSCache()
        cache.name = "ImageStore.ImageStore.cache"
        return cache
    }()

    private lazy var queue: OperationQueue = {
        let queue = OperationQueue()
        queue.name = "ImageStore.ImageStore.queue"
        return queue
    }()

    private lazy var session: URLSession = {
        let config: URLSessionConfiguration = .default
        config.timeoutIntervalForRequest = 30
        config.timeoutIntervalForResource = 30
        let session =
            URLSession(
                configuration: config,
                delegate: self,
                delegateQueue: self.queue)
        return session
    }()

    private init(_ config: ImageStoreConfig) {
        self.config = config
        super.init()
        cache.totalCostLimit = Int(self.config.cacheLimit)
    }

    @discardableResult
    public func load(_ url: URL, completion: ImageStoreCompletionHandler? = nil) -> URLSessionDownloadTask? {
        if let cachedImage: UIImage = cache.object(forKey: url.absoluteString as AnyObject) {
            completion?(cachedImage)
            return nil
        }

        if let completion = completion {
            if let completions: [ImageStoreCompletionHandler] = completionsByURLString[url.absoluteString] {
                completionsByURLString[url.absoluteString] = completions.appended(completion)
            } else {
                completionsByURLString[url.absoluteString] = [completion]
            }
        }

        let task: URLSessionDownloadTask
        if let _task = downloadTaskByURLString[url.absoluteString] {
            task = _task
            task.resume()
        } else {
            task = session.downloadTask(with: url)
            task.resume()
            downloadTaskByURLString[url.absoluteString] = task
        }

        return task
    }

    public func suspendIfResuming(url: URL) {
        if let task = downloadTaskByURLString[url.absoluteString] {
            task.suspend()
        }
    }

    public func cancel(url: URL) {
        if let task = downloadTaskByURLString[url.absoluteString] {
            task.cancel()
            downloadTaskByURLString.removeValue(forKey: url.absoluteString)
        }
    }

}

extension ImageStore: URLSessionDownloadDelegate {
    public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        do {
            let data = try Data(contentsOf: location)
            if let image = UIImage(data: data), let url = downloadTask.currentRequest?.url {
                cache.setObject(image, forKey: url.absoluteString as AnyObject)
                if let _ = downloadTaskByURLString[url.absoluteString] {
                    downloadTaskByURLString.removeValue(forKey: url.absoluteString)
                }
                guard let completions: [ImageStoreCompletionHandler] = completionsByURLString[url.absoluteString] else {
                    return
                }
                DispatchQueue.main.async { [weak self] in
                    completions.forEach { $0(image) }
                    self?.completionsByURLString[url.absoluteString] = []
                }
            } else {
                print("[ImageStore] can't instantiate image from data.")
            }
        } catch {
            print("[ImageStore] can't get data from url.")
        }
    }
<commit_msg>add cache cost<commit_after>//
//  ImageStore.swift
//  ImageStore
//
//  Created by miup on 2017/08/01.
//

import UIKit

public struct ImageStoreConfig {

    /// default 20MB
    let maxDownloadSize: Int64

    /// default 200MB
    let cacheLimit: Int64

    public init(maxDownloadSize: Int64 = Int64(20e6), cacheLimit: Int64 = Int64(200e6)) {
        self.maxDownloadSize = maxDownloadSize
        self.cacheLimit = cacheLimit
    }
}

public final class ImageStore: NSObject {

    public typealias ImageStoreCompletionHandler = ((UIImage?) -> Void)

    private(set) public static var shared: ImageStore = ImageStore(ImageStoreConfig())

    public class func reset(config: ImageStoreConfig = ImageStoreConfig()) {
        shared = ImageStore(config)
    }


    fileprivate let config: ImageStoreConfig
    fileprivate var completionsByURLString: [String: [ImageStoreCompletionHandler]] = [:]
    fileprivate var downloadTaskByURLString: [String: URLSessionDownloadTask] = [:]

    private(set) lazy var cache: NSCache<AnyObject, UIImage> = {
        let cache: NSCache<AnyObject, UIImage> = NSCache()
        cache.name = "ImageStore.ImageStore.cache"
        return cache
    }()

    private lazy var queue: OperationQueue = {
        let queue = OperationQueue()
        queue.name = "ImageStore.ImageStore.queue"
        return queue
    }()

    private lazy var session: URLSession = {
        let config: URLSessionConfiguration = .default
        config.timeoutIntervalForRequest = 30
        config.timeoutIntervalForResource = 30
        let session =
            URLSession(
                configuration: config,
                delegate: self,
                delegateQueue: self.queue)
        return session
    }()

    private init(_ config: ImageStoreConfig) {
        self.config = config
        super.init()
        cache.totalCostLimit = Int(self.config.cacheLimit)
    }

    @discardableResult
    public func load(_ url: URL, completion: ImageStoreCompletionHandler? = nil) -> URLSessionDownloadTask? {
        if let cachedImage: UIImage = cache.object(forKey: url.absoluteString as AnyObject) {
            completion?(cachedImage)
            return nil
        }

        if let completion = completion {
            if let completions: [ImageStoreCompletionHandler] = completionsByURLString[url.absoluteString] {
                completionsByURLString[url.absoluteString] = completions.appended(completion)
            } else {
                completionsByURLString[url.absoluteString] = [completion]
            }
        }

        let task: URLSessionDownloadTask
        if let _task = downloadTaskByURLString[url.absoluteString] {
            task = _task
            task.resume()
        } else {
            task = session.downloadTask(with: url)
            task.resume()
            downloadTaskByURLString[url.absoluteString] = task
        }

        return task
    }

    public func suspendIfResuming(url: URL) {
        if let task = downloadTaskByURLString[url.absoluteString] {
            task.suspend()
        }
    }

    public func cancel(url: URL) {
        if let task = downloadTaskByURLString[url.absoluteString] {
            task.cancel()
            downloadTaskByURLString.removeValue(forKey: url.absoluteString)
        }
    }

}

extension ImageStore: URLSessionDownloadDelegate {
    public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        do {
            let data = try Data(contentsOf: location)
            if let image = UIImage(data: data), let url = downloadTask.currentRequest?.url {
                cache.setObject(image, forKey: url.absoluteString as AnyObject, cost: data.count)
                if let _ = downloadTaskByURLString[url.absoluteString] {
                    downloadTaskByURLString.removeValue(forKey: url.absoluteString)
                }
                guard let completions: [ImageStoreCompletionHandler] = completionsByURLString[url.absoluteString] else {
                    return
                }
                DispatchQueue.main.async { [weak self] in
                    completions.forEach { $0(image) }
                    self?.completionsByURLString[url.absoluteString] = []
                }
            } else {
                print("[ImageStore] can't instantiate image from data.")
            }
        } catch {
            print("[ImageStore] can't get data from url.")
        }
    }

{'fraction_non_alphanumeric': 0.08402512901054521, 'fraction_numerical': 0.007628449629795826, 'mean_word_length': 2.242997453619498, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 42, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '504960', 'n_tokens_mistral': 2394, 'n_tokens_neox': 2272, 'n_words': 544}