<commit_before>//
//  CloudSourceTabBarController.swift
//  Filestack
//
//  Created by <NAME> on 11/16/17.
//  Copyright © 2017 Filestack. All rights reserved.
//

import Alamofire
import FilestackSDK
import UIKit

internal struct CloudSourceTabBarScene: Scene {
    let client: Client
    let storeOptions: StorageOptions
    let source: CloudSource

    var customSourceName: String?
    var path: String?
    var nextPageToken: String?
    var viewType: CloudSourceViewType

    func configureViewController(_ viewController: CloudSourceTabBarController) {
        // Inject the dependencies
        viewController.client = client
        viewController.storeOptions = storeOptions
        viewController.source = source
        viewController.customSourceName = customSourceName
        viewController.nextPageToken = nextPageToken
        viewController.path = path ?? "/"
        viewController.viewType = viewType
    }
}

internal class CloudSourceTabBarController: UITabBarController, CloudSourceDataSource {
    var client: Client!
    var storeOptions: StorageOptions!
    var source: CloudSource!
    var path: String!
    var nextPageToken: String?

    internal private(set) var items: [CloudItem]?

    let thumbnailCache: NSCache<NSURL, UIImage> = {
        let cache = NSCache<NSURL, UIImage>()

        cache.countLimit = 1000

        return cache
    }()

    var viewType: CloudSourceViewType!

    private var requestInProgress: Bool {
        return currentRequest != nil
    }

    private let sessionManager = SessionManager.filestackDefault
    private var toggleViewTypeButton: UIBarButtonItem?
    private var currentRequest: CancellableRequest?
    private var thumbnailRequests: [DataRequest] = [DataRequest]()
    private weak var uploadMonitorViewController: UploadMonitorViewController?

    fileprivate var customSourceName: String?

    // MARK: - View Overrides

    override func viewDidLoad() {
        super.viewDidLoad()

        // For all the sources except the custom source, we obtain its name by asking about its description,
        // however, for the custom source name we obtain this value separately.
        if source == .customSource, customSourceName != nil {
            title = customSourceName
        } else {
            title = source.description
        }
        // Hide tab bar (we will toggle between views using our custom list/grid toggle button.)
        tabBar.isHidden = true
        // Replace default back button with one with a custom title
        navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
        // Add logout button (unless we are displaying the custom source)
        if source != .customSource {
            let logoutImage = UIImage(named: "icon-logout", in: Bundle(for: type(of: self)), compatibleWith: nil)

            navigationItem.rightBarButtonItems = [
                UIBarButtonItem(image: logoutImage, style: .plain, target: self, action: #selector(logout)),
            ]
        } else {
            navigationItem.rightBarButtonItems = []
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        selectedIndex = viewType.rawValue
        setupViewtypeButton()

        guard items == nil else { return }

        // Request folder list, and notify the selected view controller when done.
        currentRequest = requestFolderList(source: source, path: path) { response in
            self.currentRequest = nil

            // Got an error, present error and pop to root view controller (i.e., source list selection)
            if let error = response.error {
                let alert = UIAlertController(title: "Error",
                                              message: error.localizedDescription,
                                              preferredStyle: .alert)

                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
                    // Dismiss monitor view controller, and remove strong reference to it
                    self.navigationController?.popToRootViewController(animated: true)
                }))

                self.present(alert, animated: true)

                return
            }

            // Ensure we got contents or return early
            guard let contents = response.contents else { return }
            // Flat map JSON array into CloudItem array.
            let items = contents.compactMap { CloudItem(dictionary: $0) }

            // Store next page token (or nil, if none)
            self.nextPageToken = response.nextToken
            // Finally, replace data source items with the latest received items
            self.items = items

            // Notify any data source consumer childs that the data source (this object) has received initial results.
            for child in self.children {
                (child as? CloudSourceDataSourceConsumer)?.dataSourceReceivedInitialResults(dataSource: self)
            }
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        currentRequest?.cancel()
        cancelPendingThumbnailRequests()

        super.viewWillDisappear(animated)
    }

    // MARK: - CloudSourceDataSource Protocol Functions

    func store(item: CloudItem) {
        var cancellableRequest: CancellableRequest?

        // Instantiate upload monitor controller
        let scene = UploadMonitorScene(cancellableRequest: cancellableRequest)

        guard let uploadMonitorViewController = storyboard?.instantiateViewController(for: scene) else { return }

        self.uploadMonitorViewController = uploadMonitorViewController

        present(uploadMonitorViewController, animated: true) {
            // Since we can not measure progress here, we will have to fake it.
            // Set progress to 50% after 0.25 seconds
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
                uploadMonitorViewController.updateProgress(value: 0.5)
            }
        }

        let completionHandler: ((StoreResponse) -> Void) = { response in
            // Nil the reference to the request object, so it can be properly deallocated.
            cancellableRequest = nil

            if let error = response.error {
                let alert = UIAlertController(title: "Upload Failed",
                                              message: error.localizedDescription,
                                              preferredStyle: .alert)

                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
                    // Dismiss monitor view controller, and remove strong reference to it
                    uploadMonitorViewController.dismiss(animated: true) {
                        self.uploadMonitorViewController = nil
                    }
                }))

                uploadMonitorViewController.present(alert, animated: true)
            } else {
                // Set progress to 100%
                uploadMonitorViewController.updateProgress(value: 1.0)
                // After 0.25 seconds, dismiss monitor view controller, and remove strong reference to it.
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
                    uploadMonitorViewController.dismiss(animated: true) {
                        self.uploadMonitorViewController = nil
                    }
                }
            }

            if let picker = self.navigationController as? PickerNavigationController {
                picker.pickerDelegate?.pickerStoredFile(picker: picker, response: response)
            }
        }

        cancellableRequest = client.store(provider: source.provider,
                                          path: item.path,
                                          storeOptions: storeOptions,
                                          completionHandler: completionHandler)
    }

    func loadNextPage(completionHandler: @escaping (() -> Void)) {
        guard let nextPageToken = nextPageToken, !requestInProgress else { return }

        currentRequest = requestFolderList(source: source,
                                           path: path,
                                           pageToken: nextPageToken) { response in
            self.currentRequest = nil

            guard let contents = response.contents else { return }

            let items = contents.compactMap { CloudItem(dictionary: $0) }

            self.nextPageToken = response.nextToken<commit_msg>Ensuring `pickerStoredFile(picker:respone:)` is called after monitor view controller is fully dismissed. Addresses issue #40.<commit_after>//
//  CloudSourceTabBarController.swift
//  Filestack
//
//  Created by <NAME> on 11/16/17.
//  Copyright © 2017 Filestack. All rights reserved.
//

import Alamofire
import FilestackSDK
import UIKit

internal struct CloudSourceTabBarScene: Scene {
    let client: Client
    let storeOptions: StorageOptions
    let source: CloudSource

    var customSourceName: String?
    var path: String?
    var nextPageToken: String?
    var viewType: CloudSourceViewType

    func configureViewController(_ viewController: CloudSourceTabBarController) {
        // Inject the dependencies
        viewController.client = client
        viewController.storeOptions = storeOptions
        viewController.source = source
        viewController.customSourceName = customSourceName
        viewController.nextPageToken = nextPageToken
        viewController.path = path ?? "/"
        viewController.viewType = viewType
    }
}

internal class CloudSourceTabBarController: UITabBarController, CloudSourceDataSource {
    var client: Client!
    var storeOptions: StorageOptions!
    var source: CloudSource!
    var path: String!
    var nextPageToken: String?

    internal private(set) var items: [CloudItem]?

    let thumbnailCache: NSCache<NSURL, UIImage> = {
        let cache = NSCache<NSURL, UIImage>()

        cache.countLimit = 1000

        return cache
    }()

    var viewType: CloudSourceViewType!

    private var requestInProgress: Bool {
        return currentRequest != nil
    }

    private let sessionManager = SessionManager.filestackDefault
    private var toggleViewTypeButton: UIBarButtonItem?
    private var currentRequest: CancellableRequest?
    private var thumbnailRequests: [DataRequest] = [DataRequest]()
    private weak var uploadMonitorViewController: UploadMonitorViewController?

    fileprivate var customSourceName: String?

    // MARK: - View Overrides

    override func viewDidLoad() {
        super.viewDidLoad()

        // For all the sources except the custom source, we obtain its name by asking about its description,
        // however, for the custom source name we obtain this value separately.
        if source == .customSource, customSourceName != nil {
            title = customSourceName
        } else {
            title = source.description
        }
        // Hide tab bar (we will toggle between views using our custom list/grid toggle button.)
        tabBar.isHidden = true
        // Replace default back button with one with a custom title
        navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
        // Add logout button (unless we are displaying the custom source)
        if source != .customSource {
            let logoutImage = UIImage(named: "icon-logout", in: Bundle(for: type(of: self)), compatibleWith: nil)

            navigationItem.rightBarButtonItems = [
                UIBarButtonItem(image: logoutImage, style: .plain, target: self, action: #selector(logout)),
            ]
        } else {
            navigationItem.rightBarButtonItems = []
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        selectedIndex = viewType.rawValue
        setupViewtypeButton()

        guard items == nil else { return }

        // Request folder list, and notify the selected view controller when done.
        currentRequest = requestFolderList(source: source, path: path) { response in
            self.currentRequest = nil

            // Got an error, present error and pop to root view controller (i.e., source list selection)
            if let error = response.error {
                let alert = UIAlertController(title: "Error",
                                              message: error.localizedDescription,
                                              preferredStyle: .alert)

                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
                    // Dismiss monitor view controller, and remove strong reference to it
                    self.navigationController?.popToRootViewController(animated: true)
                }))

                self.present(alert, animated: true)

                return
            }

            // Ensure we got contents or return early
            guard let contents = response.contents else { return }
            // Flat map JSON array into CloudItem array.
            let items = contents.compactMap { CloudItem(dictionary: $0) }

            // Store next page token (or nil, if none)
            self.nextPageToken = response.nextToken
            // Finally, replace data source items with the latest received items
            self.items = items

            // Notify any data source consumer childs that the data source (this object) has received initial results.
            for child in self.children {
                (child as? CloudSourceDataSourceConsumer)?.dataSourceReceivedInitialResults(dataSource: self)
            }
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        currentRequest?.cancel()
        cancelPendingThumbnailRequests()

        super.viewWillDisappear(animated)
    }

    // MARK: - CloudSourceDataSource Protocol Functions

    func store(item: CloudItem) {
        var cancellableRequest: CancellableRequest?

        // Instantiate upload monitor controller
        let scene = UploadMonitorScene(cancellableRequest: cancellableRequest)

        guard let uploadMonitorViewController = storyboard?.instantiateViewController(for: scene) else { return }

        self.uploadMonitorViewController = uploadMonitorViewController

        present(uploadMonitorViewController, animated: true) {
            // Since we can not measure progress here, we will have to fake it.
            // Set progress to 50% after 0.25 seconds
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
                uploadMonitorViewController.updateProgress(value: 0.5)
            }
        }

        let completionHandler: ((StoreResponse) -> Void) = { response in
            // Nil the reference to the request object, so it can be properly deallocated.
            cancellableRequest = nil

            let callPickerStoredFileOnDelegate: () -> Void = {
                if let picker = self.navigationController as? PickerNavigationController {
                    picker.pickerDelegate?.pickerStoredFile(picker: picker, response: response)
                }
            }

            if let error = response.error {
                let alert = UIAlertController(title: "Upload Failed",
                                              message: error.localizedDescription,
                                              preferredStyle: .alert)

                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
                    // Dismiss monitor view controller, and remove strong reference to it
                    uploadMonitorViewController.dismiss(animated: true) {
                        self.uploadMonitorViewController = nil
                        callPickerStoredFileOnDelegate()
                    }
                }))

                uploadMonitorViewController.present(alert, animated: true)
            } else {
                // Set progress to 100%
                uploadMonitorViewController.updateProgress(value: 1.0)
                // After 0.25 seconds, dismiss monitor view controller, and remove strong reference to it.
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
                    uploadMonitorViewController.dismiss(animated: true) {
                        self.uploadMonitorViewController = nil
                        callPickerStoredFileOnDelegate()
                    }
                }
            }
        }

        cancellableRequest = client.store(provider: source.provider,
                                          path: item.path,
                                          storeOptions: storeOptions,
                                          completionHandler: completionHandler)
    }

    func loadNextPage(completionHandler: @escaping (() -> Void)) {
        guard let nextPageToken = nextPageToken, !requestInProgress else { return }

        currentRequest = requestFolderList(source: source,
                                           path: path,
                                           pageToken: nextPageToken) { response in
            self.currentRequest = nil

            guard let contents = response.contents else { return }

            let items = contents.compactMap { CloudItem(dictionary: $0) }

            self.nextPageToken = response.nextToken
{'fraction_non_alphanumeric': 0.07002125337469126, 'fraction_numerical': 0.004365558044689528, 'mean_word_length': 2.0315166289395785, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 36, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5626147', 'n_tokens_mistral': 4164, 'n_tokens_neox': 3874, 'n_words': 1235}