<commit_before>//
//  Vincent.swift
//
//  Created by <NAME>

import UIKit
import MD5Digest

public typealias CompletionClosure = (error: NSError?, image: UIImage?) -> Void

public enum CacheType {
    case Automatic     // if a non stale image is contained in the cache, it is used. otherwise Protocol cache type is used
    case ForceCache    // if the image is contained in the cache (no matter how old), it is used. otherwise Protocol cache type is used
    case ForceDownload // ALWAYS creates a download task, ignores any cache headers from the server
    case Protocol      // ALWAYS creates a download task, cache headers decide if a request is actually made
}

@objc public class Vincent : NSObject {
    public class var sharedInstance: Vincent {
        struct Singleton {
            static let instance = Vincent(identifier: "shared-downloader")
        }
        return Singleton.instance
    }
    
    public lazy var prefetcher: Prefetcher = Prefetcher(vincent: self)

    var useDiskCache = true
    var timeoutInterval = 30.0
    var cacheInvalidationTimeout : NSTimeInterval = 1 * 24 * 3600
    var memoryCacheSize : Int = 64 * 1024 * 1024 {
        willSet {
            memoryCache.totalCostLimit = newValue
        }
    }

    private(set) var diskCacheFolderUrl : NSURL
    private lazy var urlSession : NSURLSession = {
        let configuration =  NSURLSessionConfiguration.defaultSessionConfiguration()
        configuration.HTTPMaximumConnectionsPerHost = 10
        configuration.HTTPShouldUsePipelining = true
       return NSURLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
    }()
    
    private lazy var memoryCache : NSCache = {
        var cache = NSCache()
        cache.totalCostLimit = self.memoryCacheSize
        return cache
    }()
    
    private lazy var keyCache : NSCache = {
        var cache = NSCache()
        cache.countLimit = 512
        return cache
    }()
    
    private var diskCacheSemaphore = dispatch_semaphore_create(1);
    private var runningDownloadsSemaphore = dispatch_semaphore_create(1);
    private var runningDownloads : Dictionary<String, NSURLSessionDownloadTask> = Dictionary<String, NSURLSessionDownloadTask>()
    
    // MARK: - Constructor
    public init(identifier: String) {
        let cachesDirectory = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true).first!
        diskCacheFolderUrl = NSURL(fileURLWithPath: cachesDirectory).URLByAppendingPathComponent(identifier, isDirectory: false)
        do {
            try NSFileManager.defaultManager().createDirectoryAtURL(diskCacheFolderUrl, withIntermediateDirectories: true, attributes: nil)
        } catch let error {
            print("Vincent: unable to create disk cache folder: \(error)")
        }
        super.init()
        NSNotificationCenter .defaultCenter().addObserver(self, selector: "appWillResignActive:", name: UIApplicationWillResignActiveNotification, object: nil)
    }
    
    // MARK: - Public methods
    public func downloadImageFromUrl(url: NSURL, cacheType: CacheType, success successBlock: ((image: UIImage) -> ())?, error errorBlock: ((error: NSError) -> ())?) -> String? {
        var image : UIImage?
        let cacheKey = transformUrlToCacheKey(url.absoluteString)
        
        if cacheType == .ForceCache {
            image = retrieveCachedImageForKey(cacheKey, ignoreLastAccessed: true)
        } else if cacheType == .Automatic {
            image = retrieveCachedImageForKey(cacheKey, ignoreLastAccessed: false)
        } else {
            image = nil
        }
                
        if image != nil {
            successBlock?(image: image!)
            return nil;
        }
        
        let uuid = NSUUID().UUIDString
        
        let request : NSURLRequest = NSURLRequest(URL: url, cachePolicy: cacheType == .ForceDownload ? .ReloadIgnoringLocalCacheData : .UseProtocolCachePolicy, timeoutInterval: timeoutInterval)
        let downloadTask = urlSession.downloadTaskWithRequest(request) {[weak self] (tmpImageUrl, response, error) -> Void in
            if let this = self {
                let taskInvalidated = self?.runningDownloads[uuid] == nil
                
                if error != nil {
                    if (!taskInvalidated) {
                        errorBlock?(error: error!)
                    }
                } else {
                    guard let tmpImageUrl = tmpImageUrl else {
                        errorBlock?(error: error ?? NSError(domain: "Vincent", code: -3, userInfo: [NSLocalizedDescriptionKey: "download error"]))
                        return
                    }

                    do {
                        try this.validateResponse(response as! NSHTTPURLResponse)
                        
                        image = UIImage(data: NSData(contentsOfFile: tmpImageUrl.path!)!)
                        if let image = image {
                            this.cacheImage(image, key: cacheKey, tempImageFile: tmpImageUrl)
                            if (!taskInvalidated) {
                                successBlock?(image: image)
                            }
                        } else if (!taskInvalidated) {
                            errorBlock?(error: NSError(domain: "Vincent", code: -2, userInfo:[NSLocalizedDescriptionKey: "unable to decode image"]))
                        }
                    } catch let error {
                        if (!taskInvalidated) {
                            errorBlock?(error: error as NSError)
                        }
                    }
                }
            }
        }
        
        dispatch_semaphore_wait(self.runningDownloadsSemaphore, DISPATCH_TIME_FOREVER)
        runningDownloads[uuid] = downloadTask
        dispatch_semaphore_signal(self.runningDownloadsSemaphore)
        downloadTask.resume()
        return uuid
    }
    
    public func storeImage(imageData: NSData?, forUrl url: NSURL?) {
        if let url = url {
            let cacheKey = transformUrlToCacheKey(url.absoluteString)<commit_msg>added support for custom headers + basic auth<commit_after>//
//  Vincent.swift
//
//  Created by <NAME>

import UIKit
import MD5Digest

public typealias CompletionClosure = (error: NSError?, image: UIImage?) -> Void

public enum CacheType {
    case Automatic     // if a non stale image is contained in the cache, it is used. otherwise Protocol cache type is used
    case ForceCache    // if the image is contained in the cache (no matter how old), it is used. otherwise Protocol cache type is used
    case ForceDownload // ALWAYS creates a download task, ignores any cache headers from the server
    case Protocol      // ALWAYS creates a download task, cache headers decide if a request is actually made
}

@objc public class Vincent : NSObject {
    public class var sharedInstance: Vincent {
        struct Singleton {
            static let instance = Vincent(identifier: "shared-downloader")
        }
        return Singleton.instance
    }
    
    public lazy var prefetcher: Prefetcher = Prefetcher(vincent: self)

    var useDiskCache = true
    var timeoutInterval = 30.0
    var cacheInvalidationTimeout : NSTimeInterval = 1 * 24 * 3600
    var memoryCacheSize : Int = 64 * 1024 * 1024 {
        willSet {
            memoryCache.totalCostLimit = newValue
        }
    }

    private(set) var diskCacheFolderUrl : NSURL
    private lazy var urlSession : NSURLSession = {
        let configuration =  NSURLSessionConfiguration.defaultSessionConfiguration()
        configuration.HTTPMaximumConnectionsPerHost = 10
        configuration.HTTPShouldUsePipelining = true
       return NSURLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
    }()
    
    private lazy var memoryCache : NSCache = {
        var cache = NSCache()
        cache.totalCostLimit = self.memoryCacheSize
        return cache
    }()
    
    private lazy var keyCache : NSCache = {
        var cache = NSCache()
        cache.countLimit = 512
        return cache
    }()
    
    private var diskCacheSemaphore = dispatch_semaphore_create(1);
    private var runningDownloadsSemaphore = dispatch_semaphore_create(1);
    private var runningDownloads = [String: NSURLSessionDownloadTask]()
    private var requestHeaders = [String: String]()
    private var credentials: NSURLCredential?
    
    // MARK: - Constructor
    public init(identifier: String) {
        let cachesDirectory = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true).first!
        diskCacheFolderUrl = NSURL(fileURLWithPath: cachesDirectory).URLByAppendingPathComponent(identifier, isDirectory: false)
        do {
            try NSFileManager.defaultManager().createDirectoryAtURL(diskCacheFolderUrl, withIntermediateDirectories: true, attributes: nil)
        } catch let error {
            print("Vincent: unable to create disk cache folder: \(error)")
        }
        super.init()
        NSNotificationCenter .defaultCenter().addObserver(self, selector: "appWillResignActive:", name: UIApplicationWillResignActiveNotification, object: nil)
    }
    
    // MARK: - Public methods
    public func downloadImageFromUrl(url: NSURL, cacheType: CacheType, success successBlock: ((image: UIImage) -> ())?, error errorBlock: ((error: NSError) -> ())?) -> String? {
        var image : UIImage?
        let cacheKey = transformUrlToCacheKey(url.absoluteString)
        
        if cacheType == .ForceCache {
            image = retrieveCachedImageForKey(cacheKey, ignoreLastAccessed: true)
        } else if cacheType == .Automatic {
            image = retrieveCachedImageForKey(cacheKey, ignoreLastAccessed: false)
        } else {
            image = nil
        }
                
        if image != nil {
            successBlock?(image: image!)
            return nil;
        }
        
        let uuid = NSUUID().UUIDString
        
        let request = NSMutableURLRequest(URL: url, cachePolicy: cacheType == .ForceDownload ? .ReloadIgnoringLocalCacheData : .UseProtocolCachePolicy, timeoutInterval: timeoutInterval)
        for (key, value) in requestHeaders {
            request.setValue(value, forHTTPHeaderField: key)
        }
        
        if let credentials = credentials, user = credentials.user, password = <PASSWORD> {
            let userPasswordString = "\(user):\(password)"
            let userPasswordData = userPasswordString.dataUsingEncoding(NSUTF8StringEncoding)
            let base64EncodedCredential = userPasswordData!.base64EncodedStringWithOptions([])
            let authString = "Basic \(base64EncodedCredential)"
            request.setValue(authString, forHTTPHeaderField: "Authorization")
        }
        
        let downloadTask = urlSession.downloadTaskWithRequest(request) {[weak self] (tmpImageUrl, response, error) -> Void in
            if let this = self {
                let taskInvalidated = self?.runningDownloads[uuid] == nil
                
                if error != nil {
                    if (!taskInvalidated) {
                        errorBlock?(error: error!)
                    }
                } else {
                    guard let tmpImageUrl = tmpImageUrl else {
                        errorBlock?(error: error ?? NSError(domain: "Vincent", code: -3, userInfo: [NSLocalizedDescriptionKey: "download error"]))
                        return
                    }

                    do {
                        try this.validateResponse(response as! NSHTTPURLResponse)
                        
                        image = UIImage(data: NSData(contentsOfFile: tmpImageUrl.path!)!)
                        if let image = image {
                            this.cacheImage(image, key: cacheKey, tempImageFile: tmpImageUrl)
                            if (!taskInvalidated) {
                                successBlock?(image: image)
                            }
                        } else if (!taskInvalidated) {
                            errorBlock?(error: NSError(domain: "Vincent", code: -2, userInfo:[NSLocalizedDescriptionKey: "unable to decode image"]))
                        }
                    } catch let error {
                        if (!taskInvalidated) {
                            errorBlock?(error: error as NSError)
                        }
                    }
                }
            }
        }
        
        dispatch_semaphore_wait(self.runningDownloadsSemaphore, DISPATCH_TIME_FOREVER)
        runningDownloads[uuid] = downloadTask
        dispatch_semaphore_signal(self.runningDownloadsSemaphore)
        downloadTask.resume()
        return uuid
    }
    
    public func setHeaderValue(value: String?, forHeaderWithName name: String) {
        requestHeaders[name] = value
    }
    
    public func setBasicAuthCredentials(user user: String, password: String) {
        credentials = NSURLCredential(user: user, password: password, persistence: .None)
    }
    
    public func storeImage(imageData: NSData?, forUrl url: NSURL?) {
        if let url = url {
            let cacheKey = transformUrlToCacheKey(url.absoluteString)
{'fraction_non_alphanumeric': 0.07802172756970295, 'fraction_numerical': 0.0050900250702727344, 'mean_word_length': 2.1327939076630176, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 37, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2185420', 'n_tokens_mistral': 3396, 'n_tokens_neox': 3292, 'n_words': 900}