<commit_before>//
//  This file is part of Blokada.
//
//  This Source Code Form is subject to the terms of the Mozilla Public
//  License, v. 2.0. If a copy of the MPL was not distributed with this
//  file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
//  Copyright © 2021 Blocka AB. All rights reserved.
//
//  @author <NAME>
//

import Foundation
import Combine
import UIKit

// Contains "main app state" mostly used in Home screen.
class AppRepo {

    var appStateHot: AnyPublisher<AppState, Never> {
        self.writeAppState.compactMap { $0 }.removeDuplicates().eraseToAnyPublisher()
    }

    var workingHot: AnyPublisher<Bool, Never> {
        self.writeWorking.compactMap { $0 }.removeDuplicates().eraseToAnyPublisher()
    }

    var pausedUntilHot: AnyPublisher<Date?, Never> {
        self.writePausedUntil.removeDuplicates().eraseToAnyPublisher()
    }

    var accountTypeHot: AnyPublisher<AccountType, Never> {
        self.writeAccountType.compactMap { $0 }.removeDuplicates().eraseToAnyPublisher()
    }

    private lazy var timer = Services.timer

    private lazy var currentlyOngoingHot = Repos.processingRepo.currentlyOngoingHot
    private lazy var accountHot = Repos.accountRepo.accountHot
    private lazy var cloudRepo = Repos.cloudRepo

    fileprivate let writeAppState = CurrentValueSubject<AppState?, Never>(nil)
    fileprivate let writeWorking = CurrentValueSubject<Bool?, Never>(nil)
    fileprivate let writePausedUntil = CurrentValueSubject<Date?, Never>(nil)
    fileprivate let writeAccountType = CurrentValueSubject<AccountType?, Never>(nil)

    fileprivate let pauseAppT = Tasker<Date?, Ignored>("pauseApp")
    fileprivate let unpauseAppT = SimpleTasker<Ignored>("unpauseApp")

    private var cancellables = Set<AnyCancellable>()
    private let recentAccountType = Atomic<AccountType>(AccountType.Libre)

    init() {
        onPauseApp()
        onUnpauseApp()
        onAnythingThatAffectsAppState_UpdateIt()
        onAccountChange_UpdateAccountType()
        onCurrentlyOngoing_ChangeWorkingState()
        onPause_WaitForExpirationToUnpause()
        loadPauseTimerState()
        emitWorkingStateOnStart()
    }

    func pauseApp(until: Date?) -> AnyPublisher<Ignored, Error> {
        return pauseAppT.send(until)
    }

    func unpauseApp() -> AnyPublisher<Ignored, Error> {
        return unpauseAppT.send()
    }

    // App can be paused with a timer (Date), or indefinitely (nil)
    private func onPauseApp() { // TODO: also pause plus
        pauseAppT.setTask { until in
            return self.appStateHot.first()
            .flatMap { it -> AnyPublisher<Ignored, Error> in
                // App is already paused, only update timer
                if it == .Paused {
                    guard let until = until else {
                        // Pause indefinitely instead
                        return self.timer.cancelTimer(NOTIF_PAUSE)
                    }

                    return self.timer.createTimer(NOTIF_PAUSE, when: until)
                } else if it == .Activated {
                    guard let until = until else {
                        // Just pause indefinitely
                        return self.cloudRepo.setPaused(true)
                    }

                    return self.cloudRepo.setPaused(true)
                    .flatMap { _ in self.timer.createTimer(NOTIF_PAUSE, when: until) }
                    .eraseToAnyPublisher()
                } else {
                    return Fail(error: "cannot pause, app in wrong state")
                    .eraseToAnyPublisher()
                }
            }
            .map { _ in
                self.writePausedUntil.send(until)
                return true
            }
            .eraseToAnyPublisher()
        }
    }

    private func onUnpauseApp() {
        unpauseAppT.setTask { _ in
            return self.appStateHot.first()
            .flatMap { it -> AnyPublisher<Ignored, Error> in
                if it == .Paused {
                    return self.cloudRepo.setPaused(false)
                    .flatMap { _ in self.timer.cancelTimer(NOTIF_PAUSE) }
                    .eraseToAnyPublisher()
                } else {
                    return Fail(error: "cannot unpause, app in wrong state")
                    .eraseToAnyPublisher()
                }
            }
            .map { _ in
                self.writePausedUntil.send(nil)
                return true
            }<commit_msg>ios: a quick fix for pause state on quite force app restart<commit_after>//
//  This file is part of Blokada.
//
//  This Source Code Form is subject to the terms of the Mozilla Public
//  License, v. 2.0. If a copy of the MPL was not distributed with this
//  file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
//  Copyright © 2021 Blocka AB. All rights reserved.
//
//  @author <NAME>
//

import Foundation
import Combine
import UIKit

// Contains "main app state" mostly used in Home screen.
class AppRepo {

    var appStateHot: AnyPublisher<AppState, Never> {
        self.writeAppState.compactMap { $0 }.removeDuplicates().eraseToAnyPublisher()
    }

    var workingHot: AnyPublisher<Bool, Never> {
        self.writeWorking.compactMap { $0 }.removeDuplicates().eraseToAnyPublisher()
    }

    var pausedUntilHot: AnyPublisher<Date?, Never> {
        self.writePausedUntil.removeDuplicates().eraseToAnyPublisher()
    }

    var accountTypeHot: AnyPublisher<AccountType, Never> {
        self.writeAccountType.compactMap { $0 }.removeDuplicates().eraseToAnyPublisher()
    }

    private lazy var timer = Services.timer

    private lazy var currentlyOngoingHot = Repos.processingRepo.currentlyOngoingHot
    private lazy var accountHot = Repos.accountRepo.accountHot
    private lazy var cloudRepo = Repos.cloudRepo

    fileprivate let writeAppState = CurrentValueSubject<AppState?, Never>(nil)
    fileprivate let writeWorking = CurrentValueSubject<Bool?, Never>(nil)
    fileprivate let writePausedUntil = CurrentValueSubject<Date?, Never>(nil)
    fileprivate let writeAccountType = CurrentValueSubject<AccountType?, Never>(nil)

    fileprivate let pauseAppT = Tasker<Date?, Ignored>("pauseApp")
    fileprivate let unpauseAppT = SimpleTasker<Ignored>("unpauseApp")

    private var cancellables = Set<AnyCancellable>()
    private let recentAccountType = Atomic<AccountType>(AccountType.Libre)

    init() {
        onPauseApp()
        onUnpauseApp()
        onAnythingThatAffectsAppState_UpdateIt()
        onAccountChange_UpdateAccountType()
        onCurrentlyOngoing_ChangeWorkingState()
        onPause_WaitForExpirationToUnpause()
        loadPauseTimerState()
        emitWorkingStateOnStart()
    }

    func pauseApp(until: Date?) -> AnyPublisher<Ignored, Error> {
        return pauseAppT.send(until)
    }

    func unpauseApp() -> AnyPublisher<Ignored, Error> {
        return unpauseAppT.send()
    }

    // App can be paused with a timer (Date), or indefinitely (nil)
    private func onPauseApp() { // TODO: also pause plus
        pauseAppT.setTask { until in
            return self.appStateHot.first()
            .flatMap { it -> AnyPublisher<Ignored, Error> in
                // App is already paused, only update timer
                if it == .Paused {
                    guard let until = until else {
                        // Pause indefinitely instead
                        return self.timer.cancelTimer(NOTIF_PAUSE)
                    }

                    return self.timer.createTimer(NOTIF_PAUSE, when: until)
                } else if it == .Activated {
                    guard let until = until else {
                        // Just pause indefinitely
                        return self.cloudRepo.setPaused(true)
                    }

                    return self.cloudRepo.setPaused(true)
                    .flatMap { _ in self.timer.createTimer(NOTIF_PAUSE, when: until) }
                    .eraseToAnyPublisher()
                } else {
                    return Fail(error: "cannot pause, app in wrong state")
                    .eraseToAnyPublisher()
                }
            }
            .map { _ in
                self.writePausedUntil.send(until)
                return true
            }
            .eraseToAnyPublisher()
        }
    }

    private func onUnpauseApp() {
        unpauseAppT.setTask { _ in
            return self.appStateHot.first()
            .flatMap { it -> AnyPublisher<Ignored, Error> in
                if it == .Paused || it == .Deactivated {
                    return self.cloudRepo.setPaused(false)
                    .flatMap { _ in self.timer.cancelTimer(NOTIF_PAUSE) }
                    .eraseToAnyPublisher()
                } else {
                    return Fail(error: "cannot unpause, app in wrong state")
                    .eraseToAnyPublisher()
                }
            }
            .map { _ in
                self.writePausedUntil.send(nil)
                return true
            }
{'fraction_non_alphanumeric': 0.08622222222222223, 'fraction_numerical': 0.0024444444444444444, 'mean_word_length': 1.973571192599934, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 45, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6517894', 'n_tokens_mistral': 2574, 'n_tokens_neox': 2377, 'n_words': 615}