Format code with SwiftFormat automatically in every build
This commit is contained in:
parent
f167ab7549
commit
7f9f0e43b2
100 changed files with 1124 additions and 1063 deletions
|
|
@ -11,9 +11,8 @@
|
|||
import UIKit
|
||||
|
||||
open class PasscodeLockPresenter {
|
||||
|
||||
fileprivate var mainWindow: UIWindow?
|
||||
fileprivate var passcodeLockWindow: UIWindow?
|
||||
private var mainWindow: UIWindow?
|
||||
private var passcodeLockWindow: UIWindow?
|
||||
|
||||
public init(mainWindow window: UIWindow?) {
|
||||
self.mainWindow = window
|
||||
|
|
@ -27,7 +26,7 @@ open class PasscodeLockPresenter {
|
|||
|
||||
// new window
|
||||
mainWindow?.endEditing(true)
|
||||
passcodeLockWindow = UIWindow(frame: self.mainWindow!.frame)
|
||||
passcodeLockWindow = UIWindow(frame: mainWindow!.frame)
|
||||
moveWindowsToFront(windowLevel: windowLevel)
|
||||
passcodeLockWindow?.isHidden = false
|
||||
|
||||
|
|
@ -46,9 +45,9 @@ open class PasscodeLockPresenter {
|
|||
passcodeLockWindow?.rootViewController = nil
|
||||
}
|
||||
|
||||
fileprivate func moveWindowsToFront(windowLevel: CGFloat?) {
|
||||
private func moveWindowsToFront(windowLevel: CGFloat?) {
|
||||
let windowLevel = windowLevel ?? UIWindow.Level.normal.rawValue
|
||||
let maxWinLevel = max(windowLevel, UIWindow.Level.normal.rawValue)
|
||||
passcodeLockWindow?.windowLevel = UIWindow.Level(rawValue: maxWinLevel + 1)
|
||||
passcodeLockWindow?.windowLevel = UIWindow.Level(rawValue: maxWinLevel + 1)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,13 @@
|
|||
// Inspired by SwiftPasscodeLock created by Yanko Dimitrov.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import LocalAuthentication
|
||||
import UIKit
|
||||
|
||||
open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||
|
||||
open var dismissCompletionCallback: (()->Void)?
|
||||
open var successCallback: (()->Void)?
|
||||
open var cancelCallback: (()->Void)?
|
||||
open var dismissCompletionCallback: (() -> Void)?
|
||||
open var successCallback: (() -> Void)?
|
||||
open var cancelCallback: (() -> Void)?
|
||||
|
||||
weak var passcodeTextField: UITextField?
|
||||
weak var biometryAuthButton: UIButton?
|
||||
|
|
@ -23,21 +22,21 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
open weak var cancelButton: UIButton?
|
||||
|
||||
var isCancellable: Bool = false
|
||||
|
||||
|
||||
private let passwordStore = PasswordStore.shared
|
||||
|
||||
open override func loadView() {
|
||||
override open func loadView() {
|
||||
super.loadView()
|
||||
|
||||
let passcodeTextField = UITextField()
|
||||
let passcodeTextField = UITextField()
|
||||
passcodeTextField.borderStyle = UITextField.BorderStyle.roundedRect
|
||||
passcodeTextField.placeholder = "EnterPasscode".localize()
|
||||
passcodeTextField.isSecureTextEntry = true
|
||||
passcodeTextField.clearButtonMode = UITextField.ViewMode.whileEditing
|
||||
passcodeTextField.delegate = self
|
||||
passcodeTextField.addTarget(self, action: #selector(self.passcodeTextFieldDidChange(_:)), for: UIControl.Event.editingChanged)
|
||||
passcodeTextField.addTarget(self, action: #selector(passcodeTextFieldDidChange(_:)), for: UIControl.Event.editingChanged)
|
||||
passcodeTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.view.addSubview(passcodeTextField)
|
||||
view.addSubview(passcodeTextField)
|
||||
self.passcodeTextField = passcodeTextField
|
||||
|
||||
view.backgroundColor = Colors.systemBackground
|
||||
|
|
@ -50,7 +49,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
biometryAuthButton.addTarget(self, action: #selector(bioButtonPressedAction(_:)), for: .touchUpInside)
|
||||
biometryAuthButton.isHidden = true
|
||||
biometryAuthButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.view.addSubview(biometryAuthButton)
|
||||
view.addSubview(biometryAuthButton)
|
||||
self.biometryAuthButton = biometryAuthButton
|
||||
|
||||
let myContext = LAContext()
|
||||
|
|
@ -71,21 +70,21 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
forgotPasscodeButton.setTitleColor(Colors.systemBlue, for: .normal)
|
||||
forgotPasscodeButton.addTarget(self, action: #selector(forgotPasscodeButtonPressedAction(_:)), for: .touchUpInside)
|
||||
// hide the forgotPasscodeButton if the native app is running
|
||||
forgotPasscodeButton.isHidden = self.isCancellable
|
||||
forgotPasscodeButton.isHidden = isCancellable
|
||||
forgotPasscodeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.view.addSubview(forgotPasscodeButton)
|
||||
view.addSubview(forgotPasscodeButton)
|
||||
self.forgotPasscodeButton = forgotPasscodeButton
|
||||
|
||||
let cancelButton = UIButton(type: .custom)
|
||||
cancelButton.setTitle("Cancel".localize(), for: .normal)
|
||||
cancelButton.setTitleColor(Colors.systemBlue, for: .normal)
|
||||
cancelButton.addTarget(self, action: #selector(passcodeLockDidCancel), for: .touchUpInside)
|
||||
cancelButton.isHidden = !self.isCancellable
|
||||
cancelButton.isHidden = !isCancellable
|
||||
cancelButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
cancelButton.contentHorizontalAlignment = UIControl.ContentHorizontalAlignment.left
|
||||
self.view.addSubview(cancelButton)
|
||||
view.addSubview(cancelButton)
|
||||
self.cancelButton = cancelButton
|
||||
|
||||
|
||||
// Display the Pass icon in the middle of the screen
|
||||
let bundle = Bundle(for: PasscodeLockViewController.self)
|
||||
let appIcon = UIImage(named: "PasscodeLockViewIcon", in: bundle, compatibleWith: nil)
|
||||
|
|
@ -94,49 +93,48 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
appIconView.translatesAutoresizingMaskIntoConstraints = false
|
||||
appIconView.layer.cornerRadius = appIconSize / 5
|
||||
appIconView.layer.masksToBounds = true
|
||||
self.view?.addSubview(appIconView)
|
||||
view?.addSubview(appIconView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
passcodeTextField.widthAnchor.constraint(equalToConstant: 250),
|
||||
passcodeTextField.heightAnchor.constraint(equalToConstant: 40),
|
||||
passcodeTextField.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
||||
passcodeTextField.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -20),
|
||||
passcodeTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
passcodeTextField.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -20),
|
||||
// above passocde
|
||||
appIconView.widthAnchor.constraint(equalToConstant: appIconSize),
|
||||
appIconView.heightAnchor.constraint(equalToConstant: appIconSize),
|
||||
appIconView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
||||
appIconView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
appIconView.bottomAnchor.constraint(equalTo: passcodeTextField.topAnchor, constant: -appIconSize),
|
||||
// below passcode
|
||||
biometryAuthButton.widthAnchor.constraint(equalToConstant: 250),
|
||||
biometryAuthButton.heightAnchor.constraint(equalToConstant: 40),
|
||||
biometryAuthButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
||||
biometryAuthButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
biometryAuthButton.topAnchor.constraint(equalTo: passcodeTextField.bottomAnchor),
|
||||
// cancel (top-left of the screen)
|
||||
cancelButton.widthAnchor.constraint(equalToConstant: 150),
|
||||
cancelButton.heightAnchor.constraint(equalToConstant: 40),
|
||||
cancelButton.topAnchor.constraint(equalTo: self.view.safeTopAnchor),
|
||||
cancelButton.leftAnchor.constraint(equalTo: self.view.safeLeftAnchor, constant: 20),
|
||||
cancelButton.topAnchor.constraint(equalTo: view.safeTopAnchor),
|
||||
cancelButton.leftAnchor.constraint(equalTo: view.safeLeftAnchor, constant: 20),
|
||||
// bottom of the screen
|
||||
forgotPasscodeButton.widthAnchor.constraint(equalToConstant: 250),
|
||||
forgotPasscodeButton.heightAnchor.constraint(equalToConstant: 40),
|
||||
forgotPasscodeButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
||||
forgotPasscodeButton.bottomAnchor.constraint(equalTo: self.view.safeBottomAnchor, constant: -40)
|
||||
forgotPasscodeButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
forgotPasscodeButton.bottomAnchor.constraint(equalTo: view.safeBottomAnchor, constant: -40),
|
||||
])
|
||||
|
||||
// dismiss keyboard when tapping anywhere
|
||||
let tap = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing))
|
||||
let tap = UITapGestureRecognizer(target: view, action: #selector(UIView.endEditing))
|
||||
view.addGestureRecognizer(tap)
|
||||
|
||||
}
|
||||
|
||||
open override func viewDidLoad() {
|
||||
override open func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
open override func viewDidAppear(_ animated: Bool) {
|
||||
override open func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
if let biometryAuthButton = biometryAuthButton {
|
||||
self.bioButtonPressedAction(biometryAuthButton)
|
||||
bioButtonPressedAction(biometryAuthButton)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,16 +165,18 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
dismissPasscodeLock(completionHandler: successCallback)
|
||||
}
|
||||
|
||||
@objc func passcodeLockDidCancel() {
|
||||
@objc
|
||||
func passcodeLockDidCancel() {
|
||||
dismissPasscodeLock(completionHandler: cancelCallback)
|
||||
}
|
||||
|
||||
@objc func bioButtonPressedAction(_ uiButton: UIButton) {
|
||||
@objc
|
||||
func bioButtonPressedAction(_: UIButton) {
|
||||
let myContext = LAContext()
|
||||
let myLocalizedReasonString = "AuthenticationNeeded.".localize()
|
||||
var authError: NSError?
|
||||
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
|
||||
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in
|
||||
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, _ in
|
||||
if success {
|
||||
DispatchQueue.main.async {
|
||||
// user authenticated successfully, take appropriate action
|
||||
|
|
@ -187,9 +187,10 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
@objc func forgotPasscodeButtonPressedAction(_ uiButton: UIButton) {
|
||||
@objc
|
||||
func forgotPasscodeButtonPressedAction(_: UIButton) {
|
||||
let alert = UIAlertController(title: "ResetPass".localize(), message: "ResetPassExplanation.".localize(), preferredStyle: UIAlertController.Style.alert)
|
||||
alert.addAction(UIAlertAction(title: "ErasePasswordStoreData".localize(), style: UIAlertAction.Style.destructive, handler: {[unowned self] (action) -> Void in
|
||||
alert.addAction(UIAlertAction(title: "ErasePasswordStoreData".localize(), style: UIAlertAction.Style.destructive, handler: { [unowned self] (_) -> Void in
|
||||
let myContext = LAContext()
|
||||
var error: NSError?
|
||||
// If the device passcode is not set, reset the app.
|
||||
|
|
@ -199,7 +200,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
return
|
||||
}
|
||||
// If the device passcode is set, authentication is required.
|
||||
myContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "ErasePasswordStoreData".localize()) { (success, error) in
|
||||
myContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "ErasePasswordStoreData".localize()) { success, error in
|
||||
if success {
|
||||
DispatchQueue.main.async {
|
||||
// User authenticated successfully, take appropriate action
|
||||
|
|
@ -214,25 +215,26 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
}
|
||||
}))
|
||||
alert.addAction(UIAlertAction.dismiss())
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public override func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
override public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
if textField == passcodeTextField {
|
||||
if !PasscodeLock.shared.check(passcode: textField.text ?? "") {
|
||||
self.passcodeTextField?.placeholder =
|
||||
passcodeTextField?.placeholder =
|
||||
"TryAgain".localize()
|
||||
self.passcodeTextField?.text = ""
|
||||
self.passcodeTextField?.shake()
|
||||
passcodeTextField?.text = ""
|
||||
passcodeTextField?.shake()
|
||||
}
|
||||
}
|
||||
textField.resignFirstResponder()
|
||||
return true
|
||||
}
|
||||
|
||||
@objc func passcodeTextFieldDidChange(_ textField: UITextField) {
|
||||
@objc
|
||||
func passcodeTextFieldDidChange(_ textField: UITextField) {
|
||||
if PasscodeLock.shared.check(passcode: textField.text ?? "") {
|
||||
self.passcodeLockDidSucceed()
|
||||
passcodeLockDidSucceed()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
extension Array {
|
||||
|
||||
func slices(count: UInt) -> [ArraySlice<Element>] {
|
||||
guard count != 0 else {
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@
|
|||
|
||||
extension String {
|
||||
public func localize() -> String {
|
||||
return NSLocalizedString(self, value: "#\(self)#", comment: "")
|
||||
NSLocalizedString(self, value: "#\(self)#", comment: "")
|
||||
}
|
||||
|
||||
public func localize(_ firstValue: CVarArg) -> String {
|
||||
return String(format: localize(), firstValue)
|
||||
String(format: localize(), firstValue)
|
||||
}
|
||||
|
||||
public func localize(_ firstValue: CVarArg, _ secondValue: CVarArg) -> String {
|
||||
return String(format: localize(), firstValue, secondValue)
|
||||
String(format: localize(), firstValue, secondValue)
|
||||
}
|
||||
|
||||
public func localize(_ error: Error) -> String {
|
||||
return localize(error.localizedDescription)
|
||||
localize(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
extension String {
|
||||
public var trimmed: String {
|
||||
return trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
public func stringByAddingPercentEncodingForRFC3986() -> String? {
|
||||
|
|
@ -19,12 +19,12 @@ extension String {
|
|||
}
|
||||
|
||||
public func splitByNewline() -> [String] {
|
||||
return split(omittingEmptySubsequences: false) { $0 == "\n" || $0 == "\r\n" }.map(String.init)
|
||||
split(omittingEmptySubsequences: false) { $0 == "\n" || $0 == "\r\n" }.map(String.init)
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
public static func | (left: String, right: String) -> String {
|
||||
return right.isEmpty ? left : left + "\n" + right
|
||||
right.isEmpty ? left : left + "\n" + right
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
// Copyright © 2020 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension UIAlertAction {
|
||||
public static func cancelAndPopView(controller: UIViewController) -> UIAlertAction {
|
||||
return cancel() { _ in
|
||||
cancel { _ in
|
||||
controller.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ extension UIAlertAction {
|
|||
cancel(with: "Dismiss", handler: handler)
|
||||
}
|
||||
|
||||
public static func cancel(with title: String, handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction {
|
||||
public static func cancel(with _: String, handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction {
|
||||
UIAlertAction(title: "Cancel".localize(), style: .cancel, handler: handler)
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ extension UIAlertAction {
|
|||
}
|
||||
|
||||
public static func okAndPopView(controller: UIViewController) -> UIAlertAction {
|
||||
return ok() { _ in
|
||||
ok { _ in
|
||||
controller.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
|
@ -41,13 +41,12 @@ extension UIAlertAction {
|
|||
public static func selectKey(controller: UIViewController, handler: ((UIAlertAction) -> Void)?) -> UIAlertAction {
|
||||
UIAlertAction(title: "Select Key", style: .default) { _ in
|
||||
let selectKeyAlert = UIAlertController(title: "Select from imported keys", message: nil, preferredStyle: .actionSheet)
|
||||
try? PGPAgent.shared.getShortKeyID().forEach({ k in
|
||||
try? PGPAgent.shared.getShortKeyID().forEach { k in
|
||||
let action = UIAlertAction(title: k, style: .default, handler: handler)
|
||||
selectKeyAlert.addAction(action)
|
||||
})
|
||||
}
|
||||
selectKeyAlert.addAction(UIAlertAction.cancelAndPopView(controller: controller))
|
||||
controller.present(selectKeyAlert, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,18 +14,18 @@ private var kAssociationKeyNextField: UInt8 = 0
|
|||
extension UITextField {
|
||||
@IBOutlet var nextField: UITextField? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
|
||||
objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
|
||||
}
|
||||
set(newField) {
|
||||
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func shake() {
|
||||
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||
animation.repeatCount = 3
|
||||
animation.duration = 0.2/TimeInterval(animation.repeatCount)
|
||||
animation.duration = 0.2 / TimeInterval(animation.repeatCount)
|
||||
animation.autoreverses = true
|
||||
animation.values = [3, -3]
|
||||
layer.add(animation, forKey: "shake")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
//
|
||||
|
||||
extension UIViewController {
|
||||
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
@objc
|
||||
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
if textField.nextField != nil {
|
||||
textField.nextField?.becomeFirstResponder()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -9,29 +9,28 @@
|
|||
import Foundation
|
||||
|
||||
extension UIView {
|
||||
|
||||
// Save anchors: https://stackoverflow.com/questions/46317061/use-safe-area-layout-programmatically
|
||||
var safeTopAnchor: NSLayoutYAxisAnchor {
|
||||
if #available(iOS 11.0, *) {
|
||||
return self.safeAreaLayoutGuide.topAnchor
|
||||
} else {
|
||||
return self.topAnchor
|
||||
return topAnchor
|
||||
}
|
||||
}
|
||||
|
||||
var safeLeftAnchor: NSLayoutXAxisAnchor {
|
||||
if #available(iOS 11.0, *){
|
||||
if #available(iOS 11.0, *) {
|
||||
return self.safeAreaLayoutGuide.leftAnchor
|
||||
} else {
|
||||
return self.leftAnchor
|
||||
return leftAnchor
|
||||
}
|
||||
}
|
||||
|
||||
var safeRightAnchor: NSLayoutXAxisAnchor {
|
||||
if #available(iOS 11.0, *){
|
||||
if #available(iOS 11.0, *) {
|
||||
return self.safeAreaLayoutGuide.rightAnchor
|
||||
} else {
|
||||
return self.rightAnchor
|
||||
return rightAnchor
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +38,7 @@ extension UIView {
|
|||
if #available(iOS 11.0, *) {
|
||||
return self.safeAreaLayoutGuide.bottomAnchor
|
||||
} else {
|
||||
return self.bottomAnchor
|
||||
return bottomAnchor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@
|
|||
import KeychainAccess
|
||||
|
||||
public class AppKeychain: KeyStore {
|
||||
|
||||
public static let shared = AppKeychain()
|
||||
|
||||
|
||||
private let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
|
||||
.accessibility(.whenUnlockedThisDeviceOnly)
|
||||
.synchronizable(false)
|
||||
|
|
@ -25,15 +24,15 @@ public class AppKeychain: KeyStore {
|
|||
}
|
||||
|
||||
public func contains(key: String) -> Bool {
|
||||
return (try? keychain.contains(key)) ?? false
|
||||
(try? keychain.contains(key)) ?? false
|
||||
}
|
||||
|
||||
public func get(for key: String) -> Data? {
|
||||
return try? keychain.getData(key)
|
||||
try? keychain.getData(key)
|
||||
}
|
||||
|
||||
public func get(for key: String) -> String? {
|
||||
return try? keychain.getString(key)
|
||||
try? keychain.getString(key)
|
||||
}
|
||||
|
||||
public func removeContent(for key: String) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import Foundation
|
|||
|
||||
// https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516
|
||||
public extension FileManager {
|
||||
|
||||
/// This method calculates the accumulated size of a directory on the volume in bytes.
|
||||
///
|
||||
/// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy,
|
||||
|
|
@ -19,8 +18,7 @@ public extension FileManager {
|
|||
///
|
||||
/// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
|
||||
/// directories, hard links, ...).
|
||||
func allocatedSizeOfDirectoryAtURL(directoryURL : URL) throws -> UInt64 {
|
||||
|
||||
func allocatedSizeOfDirectoryAtURL(directoryURL: URL) throws -> UInt64 {
|
||||
// We'll sum up content size here:
|
||||
var accumulatedSize = UInt64(0)
|
||||
|
||||
|
|
@ -29,7 +27,7 @@ public extension FileManager {
|
|||
URLResourceKey.isRegularFileKey,
|
||||
URLResourceKey.fileAllocatedSizeKey,
|
||||
URLResourceKey.totalFileAllocatedSizeKey,
|
||||
]
|
||||
]
|
||||
|
||||
// The error handler simply signals errors to outside code.
|
||||
var errorDidOccur: Error?
|
||||
|
|
@ -38,7 +36,6 @@ public extension FileManager {
|
|||
return false
|
||||
}
|
||||
|
||||
|
||||
// We have to enumerate all directory contents, including subdirectories.
|
||||
let enumerator = self.enumerator(at: directoryURL,
|
||||
includingPropertiesForKeys: prefetchedProperties,
|
||||
|
|
@ -91,4 +88,3 @@ public extension FileManager {
|
|||
return accumulatedSize
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public final class Globals {
|
|||
public static let gitPassword = "gitPassword"
|
||||
public static let gitSSHPrivateKeyPassphrase = "gitSSHPrivateKeyPassphrase"
|
||||
public static let pgpKeyPassphrase = "pgpKeyPassphrase"
|
||||
|
||||
|
||||
public static let gitSignatureDefaultName = "Pass for iOS"
|
||||
public static let gitSignatureDefaultEmail = "user@passforios"
|
||||
|
||||
|
|
@ -43,14 +43,15 @@ public final class Globals {
|
|||
// UI related
|
||||
public static let tableCellButtonSize = CGFloat(20.0)
|
||||
|
||||
private init() { }
|
||||
private init() {}
|
||||
}
|
||||
|
||||
public extension Bundle {
|
||||
var releaseVersionNumber: String? {
|
||||
return infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
}
|
||||
|
||||
var buildVersionNumber: String? {
|
||||
return infoDictionary?["CFBundleVersion"] as? String
|
||||
infoDictionary?["CFBundleVersion"] as? String
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,6 @@ public class KeyFileManager {
|
|||
}
|
||||
|
||||
public func doesKeyFileExist() -> Bool {
|
||||
return FileManager.default.fileExists(atPath: keyPath)
|
||||
FileManager.default.fileExists(atPath: keyPath)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static func copyToPasteboard(textToCopy: String?) {
|
||||
guard textToCopy != nil else {
|
||||
return
|
||||
|
|
@ -15,8 +14,8 @@ public class Utils {
|
|||
UIPasteboard.general.string = textToCopy
|
||||
}
|
||||
|
||||
public static func attributedPassword(plainPassword: String) -> NSAttributedString{
|
||||
let attributedPassword = NSMutableAttributedString.init(string: plainPassword)
|
||||
public static func attributedPassword(plainPassword: String) -> NSAttributedString {
|
||||
let attributedPassword = NSMutableAttributedString(string: plainPassword)
|
||||
// draw all digits in the password into red
|
||||
// draw all punctuation characters in the password into blue
|
||||
for (index, element) in plainPassword.unicodeScalars.enumerated() {
|
||||
|
|
@ -40,24 +39,24 @@ public class Utils {
|
|||
}
|
||||
|
||||
public static func createRequestPGPKeyPassphraseHandler(controller: UIViewController) -> (String) -> String {
|
||||
return { keyID in
|
||||
{ keyID in
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var passphrase = ""
|
||||
DispatchQueue.main.async {
|
||||
let title = "Passphrase".localize() + " (\(keyID.suffix(8)))"
|
||||
let message = "FillInPgpPassphrase.".localize()
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction.ok() { _ in
|
||||
alert.addAction(UIAlertAction.ok { _ in
|
||||
passphrase = alert.textFields?.first?.text ?? ""
|
||||
sem.signal()
|
||||
})
|
||||
alert.addTextField() { textField in
|
||||
alert.addTextField { textField in
|
||||
textField.text = AppKeychain.shared.get(for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID)) ?? ""
|
||||
textField.isSecureTextEntry = true
|
||||
}
|
||||
controller.present(alert, animated: true)
|
||||
}
|
||||
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
_ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
if Defaults.isRememberPGPPassphraseOn {
|
||||
AppKeychain.shared.add(string: passphrase, for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID))
|
||||
}
|
||||
|
|
@ -65,4 +64,3 @@ public class Utils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public struct GitCredential {
|
|||
public func credentialProvider(requestCredentialPassword: @escaping (Credential, String?) -> String?) throws -> GTCredentialProvider {
|
||||
var attempts = 0
|
||||
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
|
||||
var credential: GTCredential? = nil
|
||||
var credential: GTCredential?
|
||||
|
||||
switch self.credential {
|
||||
case let .http(userName):
|
||||
|
|
@ -52,7 +52,7 @@ public struct GitCredential {
|
|||
return nil
|
||||
}
|
||||
var lastPassword = self.passwordStore.gitSSHPrivateKeyPassphrase
|
||||
if lastPassword == nil || attempts != 0 {
|
||||
if lastPassword == nil || attempts != 0 {
|
||||
if let requestedPassword = requestCredentialPassword(self.credential, lastPassword) {
|
||||
if Defaults.isRememberGitCredentialPassphraseOn {
|
||||
self.passwordStore.gitSSHPrivateKeyPassphrase = requestedPassword
|
||||
|
|
@ -72,10 +72,9 @@ public struct GitCredential {
|
|||
public func delete() {
|
||||
switch credential {
|
||||
case .http:
|
||||
self.passwordStore.gitPassword = nil
|
||||
passwordStore.gitPassword = nil
|
||||
case .ssh:
|
||||
self.passwordStore.gitSSHPrivateKeyPassphrase = nil
|
||||
passwordStore.gitSSHPrivateKeyPassphrase = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public class PasscodeLock {
|
|||
}
|
||||
|
||||
public var hasPasscode: Bool {
|
||||
return passcode != nil
|
||||
passcode != nil
|
||||
}
|
||||
|
||||
public func save(passcode: String) {
|
||||
|
|
@ -32,7 +32,7 @@ public class PasscodeLock {
|
|||
}
|
||||
|
||||
public func check(passcode: String) -> Bool {
|
||||
return self.passcode == passcode
|
||||
self.passcode == passcode
|
||||
}
|
||||
|
||||
public func delete() {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import OneTimePassword
|
||||
import Base32
|
||||
import OneTimePassword
|
||||
|
||||
public class Password {
|
||||
|
||||
public var name: String
|
||||
public var url: URL
|
||||
public var plainText: String
|
||||
|
|
@ -29,47 +28,47 @@ public class Password {
|
|||
}
|
||||
|
||||
public var namePath: String {
|
||||
return url.deletingPathExtension().path
|
||||
url.deletingPathExtension().path
|
||||
}
|
||||
|
||||
public var nameFromPath: String? {
|
||||
return url.deletingPathExtension().path.split(separator: "/").last.map { String($0) }
|
||||
url.deletingPathExtension().path.split(separator: "/").last.map { String($0) }
|
||||
}
|
||||
|
||||
public var password: String {
|
||||
return parser.firstLine
|
||||
parser.firstLine
|
||||
}
|
||||
|
||||
public var plainData: Data {
|
||||
return plainText.data(using: .utf8)!
|
||||
plainText.data(using: .utf8)!
|
||||
}
|
||||
|
||||
public var additionsPlainText: String {
|
||||
return parser.additionsSection
|
||||
parser.additionsSection
|
||||
}
|
||||
|
||||
public var username: String? {
|
||||
return getAdditionValue(withKey: Constants.USERNAME_KEYWORD)
|
||||
getAdditionValue(withKey: Constants.USERNAME_KEYWORD)
|
||||
}
|
||||
|
||||
public var login: String? {
|
||||
return getAdditionValue(withKey: Constants.LOGIN_KEYWORD)
|
||||
getAdditionValue(withKey: Constants.LOGIN_KEYWORD)
|
||||
}
|
||||
|
||||
public var urlString: String? {
|
||||
return getAdditionValue(withKey: Constants.URL_KEYWORD)
|
||||
getAdditionValue(withKey: Constants.URL_KEYWORD)
|
||||
}
|
||||
|
||||
public var currentOtp: String? {
|
||||
return otpToken?.currentPassword
|
||||
otpToken?.currentPassword
|
||||
}
|
||||
|
||||
public var numberOfUnknowns: Int {
|
||||
return additions.map { $0.title }.filter(Constants.isUnknown).count
|
||||
additions.map(\.title).filter(Constants.isUnknown).count
|
||||
}
|
||||
|
||||
public var numberOfOtpRelated: Int {
|
||||
return additions.map { $0.title }.filter(Constants.isOtpKeyword).count - (firstLineIsOTPField ? 1 : 0)
|
||||
additions.map(\.title).filter(Constants.isOtpKeyword).count - (firstLineIsOTPField ? 1 : 0)
|
||||
}
|
||||
|
||||
public init(name: String, url: URL, plainText: String) {
|
||||
|
|
@ -119,7 +118,7 @@ public class Password {
|
|||
}
|
||||
|
||||
public func getFilteredAdditions() -> [AdditionField] {
|
||||
return additions.filter { field in
|
||||
additions.filter { field in
|
||||
let title = field.title.lowercased()
|
||||
return title != Constants.USERNAME_KEYWORD
|
||||
&& title != Constants.LOGIN_KEYWORD
|
||||
|
|
@ -194,12 +193,12 @@ public class Password {
|
|||
newOtpauth?.append("&secret=")
|
||||
newOtpauth?.append(MF_Base32Codec.base32String(from: otpToken?.generator.secret))
|
||||
|
||||
var lines : [String] = []
|
||||
self.plainText.enumerateLines() { line, _ in
|
||||
var lines: [String] = []
|
||||
plainText.enumerateLines { line, _ in
|
||||
let (key, _) = Parser.getKeyValuePair(from: line)
|
||||
if !Constants.OTP_KEYWORDS.contains(key ?? "") {
|
||||
lines.append(line)
|
||||
} else if key == Constants.OTPAUTH && newOtpauth != nil {
|
||||
} else if key == Constants.OTPAUTH, newOtpauth != nil {
|
||||
lines.append(newOtpauth!)
|
||||
// set to nil to prevent duplication
|
||||
newOtpauth = nil
|
||||
|
|
@ -208,10 +207,10 @@ public class Password {
|
|||
if newOtpauth != nil {
|
||||
lines.append(newOtpauth!)
|
||||
}
|
||||
self.updatePassword(name: self.name, url: self.url, plainText: lines.joined(separator: "\n"))
|
||||
updatePassword(name: name, url: url, plainText: lines.joined(separator: "\n"))
|
||||
|
||||
// get and return the password
|
||||
return self.otpToken?.currentPassword
|
||||
return otpToken?.currentPassword
|
||||
}
|
||||
|
||||
public func getUsernameForCompletion() -> String {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import Foundation
|
|||
import SwiftyUserDefaults
|
||||
|
||||
extension PasswordEntity {
|
||||
|
||||
public var nameWithCategory: String {
|
||||
if let p = path, p.hasSuffix(".gpg") {
|
||||
return String(p.prefix(upTo: p.index(p.endIndex, offsetBy: -4)))
|
||||
|
|
@ -19,7 +18,7 @@ extension PasswordEntity {
|
|||
}
|
||||
|
||||
public func getCategoryText() -> String {
|
||||
return getCategoryArray().joined(separator: " > ")
|
||||
getCategoryArray().joined(separator: " > ")
|
||||
}
|
||||
|
||||
public func getCategoryArray() -> [String] {
|
||||
|
|
@ -44,17 +43,16 @@ extension PasswordEntity {
|
|||
// manually write models instead auto generation.
|
||||
|
||||
public func getImage() -> Data? {
|
||||
return image
|
||||
image
|
||||
}
|
||||
|
||||
public func getName() -> String {
|
||||
// unwrap non-optional core data
|
||||
return name ?? ""
|
||||
name ?? ""
|
||||
}
|
||||
|
||||
public func getPath() -> String {
|
||||
// unwrap non-optional core data
|
||||
return path ?? ""
|
||||
path ?? ""
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import ObjectiveGit
|
||||
import Foundation
|
||||
import KeychainAccess
|
||||
import ObjectiveGit
|
||||
import SwiftyUserDefaults
|
||||
import UIKit
|
||||
|
||||
public class PasswordStore {
|
||||
public static let shared = PasswordStore()
|
||||
|
|
@ -21,43 +21,40 @@ public class PasswordStore {
|
|||
dateFormatter.timeStyle = .short
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
public var storeURL: URL
|
||||
public var tempStoreURL: URL {
|
||||
get {
|
||||
URL(fileURLWithPath: "\(storeURL.path)-temp")
|
||||
}
|
||||
URL(fileURLWithPath: "\(storeURL.path)-temp")
|
||||
}
|
||||
|
||||
public var storeRepository: GTRepository?
|
||||
|
||||
|
||||
public var gitSignatureForNow: GTSignature? {
|
||||
get {
|
||||
let gitSignatureName = Defaults.gitSignatureName ?? Globals.gitSignatureDefaultName
|
||||
let gitSignatureEmail = Defaults.gitSignatureEmail ?? Globals.gitSignatureDefaultEmail
|
||||
return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date())
|
||||
}
|
||||
let gitSignatureName = Defaults.gitSignatureName ?? Globals.gitSignatureDefaultName
|
||||
let gitSignatureEmail = Defaults.gitSignatureEmail ?? Globals.gitSignatureDefaultEmail
|
||||
return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date())
|
||||
}
|
||||
|
||||
|
||||
public var gitPassword: String? {
|
||||
set {
|
||||
AppKeychain.shared.add(string: newValue, for: Globals.gitPassword)
|
||||
}
|
||||
get {
|
||||
return AppKeychain.shared.get(for: Globals.gitPassword)
|
||||
AppKeychain.shared.get(for: Globals.gitPassword)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var gitSSHPrivateKeyPassphrase: String? {
|
||||
set {
|
||||
AppKeychain.shared.add(string: newValue, for: Globals.gitSSHPrivateKeyPassphrase)
|
||||
}
|
||||
get {
|
||||
return AppKeychain.shared.get(for: Globals.gitSSHPrivateKeyPassphrase)
|
||||
AppKeychain.shared.get(for: Globals.gitSSHPrivateKeyPassphrase)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private let fm = FileManager.default
|
||||
lazy private var context: NSManagedObjectContext = {
|
||||
private lazy var context: NSManagedObjectContext = {
|
||||
let modelURL = Bundle(identifier: Globals.passKitBundleIdentifier)!.url(forResource: "pass", withExtension: "momd")!
|
||||
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
|
||||
let container = NSPersistentContainer(name: "pass", managedObjectModel: managedObjectModel!)
|
||||
|
|
@ -65,11 +62,11 @@ public class PasswordStore {
|
|||
try! FileManager.default.createDirectory(atPath: Globals.documentPath, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: URL(fileURLWithPath: Globals.dbPath))]
|
||||
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
|
||||
container.loadPersistentStores(completionHandler: { _, error in
|
||||
if let error = error as NSError? {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
|
||||
|
||||
/*
|
||||
Typical reasons for an error here include:
|
||||
* The parent directory does not exist, cannot be created, or disallows writing.
|
||||
|
|
@ -83,36 +80,36 @@ public class PasswordStore {
|
|||
})
|
||||
return container.viewContext
|
||||
}()
|
||||
|
||||
public var numberOfPasswords : Int {
|
||||
return self.fetchPasswordEntityCoreData(withDir: false).count
|
||||
|
||||
public var numberOfPasswords: Int {
|
||||
fetchPasswordEntityCoreData(withDir: false).count
|
||||
}
|
||||
|
||||
public var sizeOfRepositoryByteCount : UInt64 {
|
||||
return (try? fm.allocatedSizeOfDirectoryAtURL(directoryURL: self.storeURL)) ?? 0
|
||||
|
||||
public var sizeOfRepositoryByteCount: UInt64 {
|
||||
(try? fm.allocatedSizeOfDirectoryAtURL(directoryURL: storeURL)) ?? 0
|
||||
}
|
||||
|
||||
|
||||
public var numberOfLocalCommits: Int {
|
||||
return (try? getLocalCommits())?.flatMap { $0.count } ?? 0
|
||||
(try? getLocalCommits()).map(\.count) ?? 0
|
||||
}
|
||||
|
||||
|
||||
public var lastSyncedTime: Date? {
|
||||
return Defaults.lastSyncedTime
|
||||
Defaults.lastSyncedTime
|
||||
}
|
||||
|
||||
|
||||
public var numberOfCommits: UInt? {
|
||||
return storeRepository?.numberOfCommits(inCurrentBranch: nil)
|
||||
storeRepository?.numberOfCommits(inCurrentBranch: nil)
|
||||
}
|
||||
|
||||
init(url: URL = URL(fileURLWithPath: "\(Globals.repositoryPath)")) {
|
||||
storeURL = url
|
||||
self.storeURL = url
|
||||
|
||||
// Migration
|
||||
importExistingKeysIntoKeychain()
|
||||
|
||||
do {
|
||||
if fm.fileExists(atPath: storeURL.path) {
|
||||
try storeRepository = GTRepository.init(url: storeURL)
|
||||
try self.storeRepository = GTRepository(url: storeURL)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
|
|
@ -128,12 +125,12 @@ public class PasswordStore {
|
|||
Defaults.remove(\.pgpPrivateKeyArmor)
|
||||
Defaults.remove(\.gitSSHPrivateKeyArmor)
|
||||
}
|
||||
|
||||
|
||||
public func repositoryExists() -> Bool {
|
||||
let fm = FileManager()
|
||||
return fm.fileExists(atPath: Globals.repositoryPath)
|
||||
}
|
||||
|
||||
|
||||
public func passwordExisted(password: Password) -> Bool {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
|
|
@ -148,7 +145,7 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswordEntities".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func passwordEntityExisted(path: String) -> Bool {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
|
|
@ -163,7 +160,7 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswordEntities".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func getPasswordEntity(by path: String, isDir: Bool) -> PasswordEntity? {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
|
|
@ -173,7 +170,7 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswordEntities".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func cloneRepository(remoteRepoURL: URL,
|
||||
credential: GitCredential,
|
||||
branchName: String,
|
||||
|
|
@ -183,23 +180,23 @@ public class PasswordStore {
|
|||
do {
|
||||
let credentialProvider = try credential.credentialProvider(requestCredentialPassword: requestCredentialPassword)
|
||||
let options = [GTRepositoryCloneOptionsCredentialProvider: credentialProvider]
|
||||
try self.cloneRepository(remoteRepoURL: remoteRepoURL, options: options, branchName: branchName, transferProgressBlock: transferProgressBlock, checkoutProgressBlock: checkoutProgressBlock)
|
||||
try cloneRepository(remoteRepoURL: remoteRepoURL, options: options, branchName: branchName, transferProgressBlock: transferProgressBlock, checkoutProgressBlock: checkoutProgressBlock)
|
||||
} catch {
|
||||
credential.delete()
|
||||
throw(error)
|
||||
throw (error)
|
||||
}
|
||||
}
|
||||
|
||||
public func cloneRepository(remoteRepoURL: URL,
|
||||
options: [AnyHashable : Any]? = nil,
|
||||
options: [AnyHashable: Any]? = nil,
|
||||
branchName: String,
|
||||
transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void,
|
||||
checkoutProgressBlock: @escaping (String, UInt, UInt) -> Void,
|
||||
completion: @escaping () -> Void = {}) throws {
|
||||
try? fm.removeItem(at: storeURL)
|
||||
try? fm.removeItem(at: tempStoreURL)
|
||||
self.gitPassword = nil
|
||||
self.gitSSHPrivateKeyPassphrase = nil
|
||||
gitPassword = nil
|
||||
gitSSHPrivateKeyPassphrase = nil
|
||||
do {
|
||||
storeRepository = try GTRepository.clone(from: remoteRepoURL,
|
||||
toWorkingDirectory: tempStoreURL,
|
||||
|
|
@ -216,7 +213,7 @@ public class PasswordStore {
|
|||
self.deleteCoreData(entityName: "PasswordEntity")
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
throw(error)
|
||||
throw (error)
|
||||
}
|
||||
Defaults.lastSyncedTime = Date()
|
||||
DispatchQueue.main.async {
|
||||
|
|
@ -225,7 +222,7 @@ public class PasswordStore {
|
|||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func checkoutAndChangeBranch(withName localBranchName: String, progressBlock: @escaping (String, UInt, UInt) -> Void) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -241,7 +238,7 @@ public class PasswordStore {
|
|||
try storeRepository.checkoutReference(localBranch.reference, options: checkoutOptions)
|
||||
try storeRepository.moveHEAD(to: localBranch.reference)
|
||||
}
|
||||
|
||||
|
||||
public func pullRepository(credential: GitCredential,
|
||||
requestCredentialPassword: @escaping (GitCredential.Credential, String?) -> String?,
|
||||
progressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||
|
|
@ -253,30 +250,30 @@ public class PasswordStore {
|
|||
let remote = try GTRemote(name: "origin", in: storeRepository)
|
||||
try storeRepository.pull(storeRepository.currentBranch(), from: remote, withOptions: options, progress: progressBlock)
|
||||
Defaults.lastSyncedTime = Date()
|
||||
self.setAllSynced()
|
||||
setAllSynced()
|
||||
DispatchQueue.main.async {
|
||||
self.updatePasswordEntityCoreData()
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func updatePasswordEntityCoreData() {
|
||||
deleteCoreData(entityName: "PasswordEntity")
|
||||
do {
|
||||
var q = try fm.contentsOfDirectory(atPath: self.storeURL.path).filter {
|
||||
var q = try fm.contentsOfDirectory(atPath: storeURL.path).filter {
|
||||
!$0.hasPrefix(".")
|
||||
}.map { (filename) -> PasswordEntity in
|
||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||
if filename.hasSuffix(".gpg") {
|
||||
passwordEntity.name = String(filename.prefix(upTo: filename.index(filename.endIndex, offsetBy: -4)))
|
||||
} else {
|
||||
passwordEntity.name = filename
|
||||
}
|
||||
passwordEntity.path = filename
|
||||
passwordEntity.parent = nil
|
||||
return passwordEntity
|
||||
}.map { (filename) -> PasswordEntity in
|
||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||
if filename.hasSuffix(".gpg") {
|
||||
passwordEntity.name = String(filename.prefix(upTo: filename.index(filename.endIndex, offsetBy: -4)))
|
||||
} else {
|
||||
passwordEntity.name = filename
|
||||
}
|
||||
passwordEntity.path = filename
|
||||
passwordEntity.parent = nil
|
||||
return passwordEntity
|
||||
}
|
||||
while q.count > 0 {
|
||||
while !q.isEmpty {
|
||||
let e = q.first!
|
||||
q.remove(at: 0)
|
||||
guard !e.name!.hasPrefix(".") else {
|
||||
|
|
@ -309,9 +306,9 @@ public class PasswordStore {
|
|||
} catch {
|
||||
print(error)
|
||||
}
|
||||
self.saveUpdatedContext()
|
||||
saveUpdatedContext()
|
||||
}
|
||||
|
||||
|
||||
public func getRecentCommits(count: Int) throws -> [GTCommit] {
|
||||
guard let storeRepository = storeRepository else {
|
||||
return []
|
||||
|
|
@ -328,7 +325,7 @@ public class PasswordStore {
|
|||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
|
||||
public func fetchPasswordEntityCoreData(parent: PasswordEntity?) -> [PasswordEntity] {
|
||||
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
|
|
@ -339,7 +336,7 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswords".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func fetchPasswordEntityCoreData(withDir: Bool) -> [PasswordEntity] {
|
||||
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
|
|
@ -352,8 +349,7 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswords".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func fetchUnsyncedPasswords() -> [PasswordEntity] {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
|
||||
|
|
@ -364,17 +360,17 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswords".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func setAllSynced() {
|
||||
let passwordEntities = fetchUnsyncedPasswords()
|
||||
if passwordEntities.count > 0 {
|
||||
if !passwordEntities.isEmpty {
|
||||
for passwordEntity in passwordEntities {
|
||||
passwordEntity.synced = true
|
||||
}
|
||||
self.saveUpdatedContext()
|
||||
saveUpdatedContext()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func getLatestUpdateInfo(filename: String) -> String {
|
||||
guard let storeRepository = storeRepository else {
|
||||
return "Unknown".localize()
|
||||
|
|
@ -383,7 +379,7 @@ public class PasswordStore {
|
|||
let latestCommitTime = blameHunks.map({
|
||||
$0.finalSignature?.time?.timeIntervalSince1970 ?? 0
|
||||
}).max() else {
|
||||
return "Unknown".localize()
|
||||
return "Unknown".localize()
|
||||
}
|
||||
let lastCommitDate = Date(timeIntervalSince1970: latestCommitTime)
|
||||
if Date().timeIntervalSince(lastCommitDate) <= 60 {
|
||||
|
|
@ -391,10 +387,9 @@ public class PasswordStore {
|
|||
}
|
||||
return PasswordStore.dateFormatter.string(from: lastCommitDate)
|
||||
}
|
||||
|
||||
public func updateRemoteRepo() {
|
||||
}
|
||||
|
||||
|
||||
public func updateRemoteRepo() {}
|
||||
|
||||
private func gitAdd(path: String) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -402,7 +397,7 @@ public class PasswordStore {
|
|||
try storeRepository.index().addFile(path)
|
||||
try storeRepository.index().write()
|
||||
}
|
||||
|
||||
|
||||
private func gitRm(path: String) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -414,7 +409,7 @@ public class PasswordStore {
|
|||
try storeRepository.index().removeFile(path)
|
||||
try storeRepository.index().write()
|
||||
}
|
||||
|
||||
|
||||
private func deleteDirectoryTree(at url: URL) throws {
|
||||
var tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
||||
var count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
||||
|
|
@ -424,12 +419,12 @@ public class PasswordStore {
|
|||
count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func createDirectoryTree(at url: URL) throws {
|
||||
let tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
||||
try fm.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
|
||||
|
||||
private func gitMv(from: String, to: String) throws {
|
||||
let fromURL = storeURL.appendingPathComponent(from)
|
||||
let toURL = storeURL.appendingPathComponent(to)
|
||||
|
|
@ -437,7 +432,7 @@ public class PasswordStore {
|
|||
try gitAdd(path: to)
|
||||
try gitRm(path: from)
|
||||
}
|
||||
|
||||
|
||||
private func gitCommit(message: String) throws -> GTCommit? {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -453,7 +448,7 @@ public class PasswordStore {
|
|||
let commit = try storeRepository.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name)
|
||||
return commit
|
||||
}
|
||||
|
||||
|
||||
private func getLocalBranch(withName branchName: String) throws -> GTBranch? {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -462,7 +457,7 @@ public class PasswordStore {
|
|||
let branches = try storeRepository.branches(withPrefix: reference)
|
||||
return branches.first
|
||||
}
|
||||
|
||||
|
||||
public func pushRepository(credential: GitCredential, requestCredentialPassword: @escaping (GitCredential.Credential, String?) -> String?, transferProgressBlock: @escaping (UInt32, UInt32, Int, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -477,12 +472,12 @@ public class PasswordStore {
|
|||
throw AppError.GitPushNotSuccessful
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func addPasswordEntities(password: Password) throws -> PasswordEntity? {
|
||||
guard !passwordExisted(password: password) else {
|
||||
throw AppError.PasswordDuplicated
|
||||
}
|
||||
|
||||
|
||||
var passwordURL = password.url
|
||||
var previousPathLength = Int.max
|
||||
var paths: [String] = []
|
||||
|
|
@ -490,20 +485,20 @@ public class PasswordStore {
|
|||
paths.append(passwordURL.path)
|
||||
passwordURL = passwordURL.deletingLastPathComponent()
|
||||
// better identify errors before saving a new password
|
||||
if passwordURL.path != "." && passwordURL.path.count >= previousPathLength {
|
||||
if passwordURL.path != ".", passwordURL.path.count >= previousPathLength {
|
||||
throw AppError.WrongPasswordFilename
|
||||
}
|
||||
previousPathLength = passwordURL.path.count
|
||||
}
|
||||
paths.reverse()
|
||||
var parentPasswordEntity: PasswordEntity? = nil
|
||||
var parentPasswordEntity: PasswordEntity?
|
||||
for path in paths {
|
||||
let isDir = !path.hasSuffix(".gpg")
|
||||
if let passwordEntity = getPasswordEntity(by: path, isDir: isDir) {
|
||||
passwordEntity.synced = false
|
||||
parentPasswordEntity = passwordEntity
|
||||
} else {
|
||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: self.context) as! PasswordEntity
|
||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||
let pathURL = URL(string: path.stringByAddingPercentEncodingForRFC3986()!)!
|
||||
if isDir {
|
||||
passwordEntity.name = pathURL.lastPathComponent
|
||||
|
|
@ -518,72 +513,73 @@ public class PasswordStore {
|
|||
}
|
||||
}
|
||||
|
||||
self.saveUpdatedContext()
|
||||
saveUpdatedContext()
|
||||
return parentPasswordEntity
|
||||
}
|
||||
|
||||
|
||||
public func add(password: Password, keyID: String? = nil) throws -> PasswordEntity? {
|
||||
try createDirectoryTree(at: password.url)
|
||||
let saveURL = storeURL.appendingPathComponent(password.url.path)
|
||||
try self.encrypt(password: password, keyID: keyID).write(to: saveURL)
|
||||
try encrypt(password: password, keyID: keyID).write(to: saveURL)
|
||||
try gitAdd(path: password.url.path)
|
||||
let _ = try gitCommit(message: "AddPassword.".localize(password.url.deletingPathExtension().path))
|
||||
_ = try gitCommit(message: "AddPassword.".localize(password.url.deletingPathExtension().path))
|
||||
let newPasswordEntity = try addPasswordEntities(password: password)
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
return newPasswordEntity
|
||||
}
|
||||
|
||||
|
||||
public func delete(passwordEntity: PasswordEntity) throws {
|
||||
let deletedFileURL = try passwordEntity.getURL()
|
||||
try gitRm(path: deletedFileURL.path)
|
||||
try deletePasswordEntities(passwordEntity: passwordEntity)
|
||||
try deleteDirectoryTree(at: deletedFileURL)
|
||||
let _ = try gitCommit(message: "RemovePassword.".localize(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!))
|
||||
_ = try gitCommit(message: "RemovePassword.".localize(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!))
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
|
||||
|
||||
public func edit(passwordEntity: PasswordEntity, password: Password, keyID: String? = nil) throws -> PasswordEntity? {
|
||||
var newPasswordEntity: PasswordEntity? = passwordEntity
|
||||
let url = try passwordEntity.getURL()
|
||||
|
||||
if password.changed&PasswordChange.content.rawValue != 0 {
|
||||
|
||||
if password.changed & PasswordChange.content.rawValue != 0 {
|
||||
let saveURL = storeURL.appendingPathComponent(url.path)
|
||||
try self.encrypt(password: password, keyID: keyID).write(to: saveURL)
|
||||
try encrypt(password: password, keyID: keyID).write(to: saveURL)
|
||||
try gitAdd(path: url.path)
|
||||
let _ = try gitCommit(message: "EditPassword.".localize(url.deletingPathExtension().path.removingPercentEncoding!))
|
||||
_ = try gitCommit(message: "EditPassword.".localize(url.deletingPathExtension().path.removingPercentEncoding!))
|
||||
newPasswordEntity = passwordEntity
|
||||
newPasswordEntity?.synced = false
|
||||
self.saveUpdatedContext()
|
||||
saveUpdatedContext()
|
||||
}
|
||||
|
||||
if password.changed&PasswordChange.path.rawValue != 0 {
|
||||
|
||||
if password.changed & PasswordChange.path.rawValue != 0 {
|
||||
let deletedFileURL = url
|
||||
// add
|
||||
try createDirectoryTree(at: password.url)
|
||||
newPasswordEntity = try addPasswordEntities(password: password)
|
||||
|
||||
|
||||
// mv
|
||||
try gitMv(from: deletedFileURL.path, to: password.url.path)
|
||||
|
||||
|
||||
// delete
|
||||
try deleteDirectoryTree(at: deletedFileURL)
|
||||
try deletePasswordEntities(passwordEntity: passwordEntity)
|
||||
let _ = try gitCommit(message: "RenamePassword.".localize(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!, password.url.deletingPathExtension().path.removingPercentEncoding!))
|
||||
_ = try gitCommit(message: "RenamePassword.".localize(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!, password.url.deletingPathExtension().path.removingPercentEncoding!))
|
||||
}
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
return newPasswordEntity
|
||||
}
|
||||
|
||||
|
||||
private func deletePasswordEntities(passwordEntity: PasswordEntity) throws {
|
||||
var current: PasswordEntity? = passwordEntity
|
||||
while current != nil && (current!.children!.count == 0 || !current!.isDir) {
|
||||
// swiftformat:disable:next isEmpty
|
||||
while current != nil, current!.children!.count == 0 || !current!.isDir {
|
||||
let parent = current!.parent
|
||||
self.context.delete(current!)
|
||||
context.delete(current!)
|
||||
current = parent
|
||||
}
|
||||
self.saveUpdatedContext()
|
||||
saveUpdatedContext()
|
||||
}
|
||||
|
||||
|
||||
public func saveUpdatedContext() {
|
||||
do {
|
||||
if context.hasChanges {
|
||||
|
|
@ -593,11 +589,11 @@ public class PasswordStore {
|
|||
fatalError("FailureToSaveContext".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func deleteCoreData(entityName: String) {
|
||||
let deleteFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
|
||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetchRequest)
|
||||
|
||||
|
||||
do {
|
||||
try context.execute(deleteRequest)
|
||||
try context.save()
|
||||
|
|
@ -606,7 +602,7 @@ public class PasswordStore {
|
|||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func updateImage(passwordEntity: PasswordEntity, image: Data?) {
|
||||
guard let image = image else {
|
||||
return
|
||||
|
|
@ -625,33 +621,33 @@ public class PasswordStore {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func erase() {
|
||||
// Delete files.
|
||||
try? fm.removeItem(at: storeURL)
|
||||
try? fm.removeItem(at: tempStoreURL)
|
||||
|
||||
|
||||
// Delete PGP key, SSH key and other secrets from the keychain.
|
||||
AppKeychain.shared.removeAllContent()
|
||||
|
||||
// Delete core data.
|
||||
deleteCoreData(entityName: "PasswordEntity")
|
||||
|
||||
|
||||
// Delete default settings.
|
||||
Defaults.removeAll()
|
||||
|
||||
|
||||
// Clean up variables inside PasswordStore.
|
||||
storeRepository = nil
|
||||
|
||||
|
||||
// Delete cache explicitly.
|
||||
PasscodeLock.shared.delete()
|
||||
PGPAgent.shared.uninitKeys()
|
||||
|
||||
|
||||
// Broadcast.
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
NotificationCenter.default.post(name: .passwordStoreErased, object: nil)
|
||||
}
|
||||
|
||||
|
||||
// return the number of discarded commits
|
||||
public func reset() throws -> Int {
|
||||
guard let storeRepository = storeRepository else {
|
||||
|
|
@ -659,26 +655,25 @@ public class PasswordStore {
|
|||
}
|
||||
// get a list of local commits
|
||||
if let localCommits = try getLocalCommits(),
|
||||
localCommits.count > 0 {
|
||||
!localCommits.isEmpty {
|
||||
// get the oldest local commit
|
||||
guard let firstLocalCommit = localCommits.last,
|
||||
firstLocalCommit.parents.count == 1,
|
||||
let newHead = firstLocalCommit.parents.first else {
|
||||
throw AppError.GitReset
|
||||
throw AppError.GitReset
|
||||
}
|
||||
try storeRepository.reset(to: newHead, resetType: .hard)
|
||||
self.setAllSynced()
|
||||
self.updatePasswordEntityCoreData()
|
||||
|
||||
setAllSynced()
|
||||
updatePasswordEntityCoreData()
|
||||
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil)
|
||||
return localCommits.count
|
||||
} else {
|
||||
return 0 // no new commit
|
||||
return 0 // no new commit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private func getLocalCommits() throws -> [GTCommit]? {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -692,7 +687,7 @@ public class PasswordStore {
|
|||
guard remoteBranch.oid != nil else {
|
||||
throw AppError.RepositoryRemoteBranchNotFound(remoteBranchName)
|
||||
}
|
||||
|
||||
|
||||
// get a list of local commits
|
||||
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteBranch)
|
||||
}
|
||||
|
|
@ -708,13 +703,13 @@ public class PasswordStore {
|
|||
let url = try passwordEntity.getURL()
|
||||
return Password(name: passwordEntity.getName(), url: url, plainText: plainText)
|
||||
}
|
||||
|
||||
|
||||
public func encrypt(password: Password, keyID: String? = nil) throws -> Data {
|
||||
let encryptedDataPath = storeURL.appendingPathComponent(password.url.path)
|
||||
let keyID = keyID ?? findGPGID(from: encryptedDataPath)
|
||||
return try PGPAgent.shared.encrypt(plainData: password.plainData, keyID: keyID)
|
||||
}
|
||||
|
||||
|
||||
public func removeGitSSHKeys() {
|
||||
try? fm.removeItem(atPath: Globals.gitSSHPrivateKeyPath)
|
||||
Defaults.remove(\.gitSSHKeySource)
|
||||
|
|
@ -727,8 +722,8 @@ public class PasswordStore {
|
|||
|
||||
public func findGPGID(from url: URL) -> String {
|
||||
var path = url
|
||||
while !FileManager.default.fileExists(atPath: path.appendingPathComponent(".gpg-id").path)
|
||||
&& path.path != "file:///" {
|
||||
while !FileManager.default.fileExists(atPath: path.appendingPathComponent(".gpg-id").path),
|
||||
path.path != "file:///" {
|
||||
path = path.deletingLastPathComponent()
|
||||
}
|
||||
path = path.appendingPathComponent(".gpg-id")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class PasswordTableEntry: NSObject {
|
|||
public let isDir: Bool
|
||||
public let synced: Bool
|
||||
public let categoryText: String
|
||||
|
||||
|
||||
public init(_ entity: PasswordEntity) {
|
||||
self.passwordEntity = entity
|
||||
self.title = entity.name!
|
||||
|
|
@ -22,23 +22,22 @@ public class PasswordTableEntry: NSObject {
|
|||
self.synced = entity.synced
|
||||
self.categoryText = entity.getCategoryText()
|
||||
}
|
||||
|
||||
|
||||
public func match(_ searchText: String) -> Bool {
|
||||
return PasswordTableEntry.match(nameWithCategory: passwordEntity.nameWithCategory, searchText: searchText)
|
||||
PasswordTableEntry.match(nameWithCategory: passwordEntity.nameWithCategory, searchText: searchText)
|
||||
}
|
||||
|
||||
|
||||
public static func match(nameWithCategory: String, searchText: String) -> Bool {
|
||||
let titleSplit = nameWithCategory.split{ !($0.isLetter || $0.isNumber || $0 == ".") }
|
||||
let titleSplit = nameWithCategory.split { !($0.isLetter || $0.isNumber || $0 == ".") }
|
||||
for str in titleSplit {
|
||||
if (str.localizedCaseInsensitiveContains(searchText)) {
|
||||
if str.localizedCaseInsensitiveContains(searchText) {
|
||||
return true
|
||||
}
|
||||
if (searchText.localizedCaseInsensitiveContains(str)) {
|
||||
if searchText.localizedCaseInsensitiveContains(str) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
public struct AdditionField: Hashable {
|
||||
|
||||
public let title: String, content: String
|
||||
|
||||
public init(title: String = "", content: String = "") {
|
||||
|
|
@ -16,7 +15,7 @@ public struct AdditionField: Hashable {
|
|||
}
|
||||
|
||||
var asString: String {
|
||||
return title.isEmpty ? content : title + ": " + content
|
||||
title.isEmpty ? content : title + ": " + content
|
||||
}
|
||||
|
||||
var asTuple: (String, String) {
|
||||
|
|
@ -25,21 +24,20 @@ public struct AdditionField: Hashable {
|
|||
}
|
||||
|
||||
extension AdditionField {
|
||||
|
||||
static func | (left: String, right: AdditionField) -> String {
|
||||
return left | right.asString
|
||||
left | right.asString
|
||||
}
|
||||
|
||||
static func | (left: AdditionField, right: String) -> String {
|
||||
return left.asString | right
|
||||
left.asString | right
|
||||
}
|
||||
|
||||
static func | (left: AdditionField, right: AdditionField) -> String {
|
||||
return left.asString | right
|
||||
left.asString | right
|
||||
}
|
||||
}
|
||||
|
||||
infix operator =>: MultiplicationPrecedence
|
||||
public func => (key: String, value: String) -> AdditionField {
|
||||
return AdditionField(title: key, content: value)
|
||||
AdditionField(title: key, content: value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
public struct Constants {
|
||||
|
||||
static let OTP_SECRET = "otp_secret"
|
||||
static let OTP_TYPE = "otp_type"
|
||||
static let OTP_ALGORITHM = "otp_algorithm"
|
||||
|
|
@ -54,7 +53,7 @@ public struct Constants {
|
|||
}
|
||||
|
||||
static func isOtpKeyword(_ keyword: String) -> Bool {
|
||||
return OTP_KEYWORDS.contains(keyword.lowercased())
|
||||
OTP_KEYWORDS.contains(keyword.lowercased())
|
||||
}
|
||||
|
||||
static func isUnknown(_ string: String) -> Bool {
|
||||
|
|
@ -63,10 +62,10 @@ public struct Constants {
|
|||
}
|
||||
|
||||
static func unknown(_ number: UInt) -> String {
|
||||
return "\(UNKNOWN) \(number)"
|
||||
"\(UNKNOWN) \(number)"
|
||||
}
|
||||
|
||||
static func getSeparator(breakingLines: Bool) -> String {
|
||||
return breakingLines ? MULTILINE_WITH_LINE_BREAK_SEPARATOR : MULTILINE_WITHOUT_LINE_BREAK_SEPARATOR
|
||||
breakingLines ? MULTILINE_WITH_LINE_BREAK_SEPARATOR : MULTILINE_WITHOUT_LINE_BREAK_SEPARATOR
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public enum OTPType: String {
|
|||
case none = "None"
|
||||
|
||||
var description: String {
|
||||
return rawValue.localize()
|
||||
rawValue.localize()
|
||||
}
|
||||
|
||||
init(token: Token?) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
class Parser {
|
||||
|
||||
let firstLine: String
|
||||
let additionsSection: String
|
||||
let purgedAdditionalLines: [String]
|
||||
|
|
@ -19,8 +18,8 @@ class Parser {
|
|||
let splittedPlainText = plainText.splitByNewline()
|
||||
|
||||
firstLine = splittedPlainText.first!
|
||||
additionsSection = splittedPlainText[1...].joined(separator: "\n")
|
||||
purgedAdditionalLines = splittedPlainText[1...].filter { !$0.isEmpty }
|
||||
self.additionsSection = splittedPlainText[1...].joined(separator: "\n")
|
||||
self.purgedAdditionalLines = splittedPlainText[1...].filter { !$0.isEmpty }
|
||||
}
|
||||
|
||||
private func getAdditionFields() -> [AdditionField] {
|
||||
|
|
@ -57,7 +56,7 @@ class Parser {
|
|||
}
|
||||
let initialBlanks = String(repeating: Constants.BLANK, count: numberInitialBlanks)
|
||||
|
||||
while i < purgedAdditionalLines.count && purgedAdditionalLines[i].starts(with: initialBlanks) {
|
||||
while i < purgedAdditionalLines.count, purgedAdditionalLines[i].starts(with: initialBlanks) {
|
||||
result.append(String(purgedAdditionalLines[i].dropFirst(numberInitialBlanks)))
|
||||
result.append(Constants.getSeparator(breakingLines: !removingLineBreaks))
|
||||
i += 1
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import OneTimePassword
|
|||
/// * digits: `6` (default: `6`, optional)
|
||||
///
|
||||
class TokenBuilder {
|
||||
|
||||
private var name: String = ""
|
||||
private var secret: Data?
|
||||
private var type: OTPType = .totp
|
||||
|
|
@ -80,7 +79,6 @@ class TokenBuilder {
|
|||
return self
|
||||
}
|
||||
|
||||
|
||||
func build() -> Token? {
|
||||
guard secret != nil, digits != nil else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
public struct PasswordGenerator: Codable {
|
||||
|
||||
private static let digits = "0123456789"
|
||||
private static let letters = "abcdefghijklmnopqrstuvwxyz"
|
||||
private static let capitalLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
|
@ -114,7 +113,7 @@ public struct PasswordGenerator: Codable {
|
|||
}
|
||||
|
||||
private func selectRandomly(count: Int, from string: String) -> [Character] {
|
||||
return (0 ..< count).map { _ in string.randomElement()! }
|
||||
(0 ..< count).map { _ in string.randomElement()! }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public enum PasswordGeneratorFlavor: String {
|
|||
case xkcd = "XKCD"
|
||||
|
||||
public var localized: String {
|
||||
return rawValue.localize()
|
||||
rawValue.localize()
|
||||
}
|
||||
|
||||
public var longNameLocalized: String {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue