Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macOS: Resetting the app #423

Merged
merged 16 commits into from
Sep 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Handle wrong scheme when pasting a custom server URL #407
- Make "Connect using TCP only" work with APIv3
- Suppress 'Renew Session' for 30 mins after authentication time #417
- macOS: Ability to reset the app #259

## 2.2.3

Expand Down
4 changes: 4 additions & 0 deletions EduVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
6F54C93825E0359200A42C8F /* AddServerViewController+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F54C93725E0359200A42C8F /* AddServerViewController+iOS.swift */; };
6F54C9E725E2D6A500A42C8F /* MainViewController+StatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F54C9E625E2D6A500A42C8F /* MainViewController+StatusItem.swift */; };
6F57338724CD1570008912D4 /* CertificateExpiryHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F57338624CD1570008912D4 /* CertificateExpiryHelper.swift */; };
6F5820F826EE036800906397 /* AppDataRemover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5820F726EE036800906397 /* AppDataRemover.swift */; };
6F59A58824F51DEB00560155 /* server_list.json in Resources */ = {isa = PBXBuildFile; fileRef = 6F59A58624F51DEB00560155 /* server_list.json */; };
6F59A58924F51DEB00560155 /* organization_list.json in Resources */ = {isa = PBXBuildFile; fileRef = 6F59A58724F51DEB00560155 /* organization_list.json */; };
6F59A58B24F67CE500560155 /* OAuthExternalUserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F59A58A24F67CE500560155 /* OAuthExternalUserAgent.swift */; };
Expand Down Expand Up @@ -405,6 +406,7 @@
6F54C93725E0359200A42C8F /* AddServerViewController+iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddServerViewController+iOS.swift"; sourceTree = "<group>"; };
6F54C9E625E2D6A500A42C8F /* MainViewController+StatusItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainViewController+StatusItem.swift"; sourceTree = "<group>"; };
6F57338624CD1570008912D4 /* CertificateExpiryHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateExpiryHelper.swift; sourceTree = "<group>"; };
6F5820F726EE036800906397 /* AppDataRemover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDataRemover.swift; sourceTree = "<group>"; };
6F59A58624F51DEB00560155 /* server_list.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = server_list.json; sourceTree = "<group>"; };
6F59A58724F51DEB00560155 /* organization_list.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = organization_list.json; sourceTree = "<group>"; };
6F59A58A24F67CE500560155 /* OAuthExternalUserAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthExternalUserAgent.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -897,6 +899,7 @@
6F49FAA8263C1A18005DB8D3 /* Mac */ = {
isa = PBXGroup;
children = (
6F5820F726EE036800906397 /* AppDataRemover.swift */,
6F49FAAA263C1A55005DB8D3 /* OAuthRedirectHTTPHandler.h */,
6F49FAAB263C1A55005DB8D3 /* OAuthRedirectHTTPHandler.m */,
);
Expand Down Expand Up @@ -2179,6 +2182,7 @@
6F59A58B24F67CE500560155 /* OAuthExternalUserAgent.swift in Sources */,
6F49FAB5263C1A56005DB8D3 /* OAuthRedirectHTTPHandler.m in Sources */,
6FEF30FF24A325860026C786 /* RowCell.swift in Sources */,
6F5820F826EE036800906397 /* AppDataRemover.swift in Sources */,
6FADF83124ADF86700B75E8D /* Log.swift in Sources */,
6FEF313B24A717570026C786 /* ServerAuthService.swift in Sources */,
6F4C1ED625D12D710042AD95 /* SharedTunnelOptions.swift in Sources */,
Expand Down
40 changes: 40 additions & 0 deletions EduVPN/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,46 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return .terminateLater
}

func resetAppAfterConfirming() {
guard let environment = self.environment else {
return
}

let alert = NSAlert()
alert.alertStyle = .warning

alert.messageText = NSLocalizedString(
"Are you sure you want to reset the app \(Config.shared.appName)?",
comment: "macOS alert title on attempt to reset app")
alert.informativeText = NSLocalizedString(
"All user data and preferences will be removed.",
comment: "macOS alert text on attempt to reset app")
alert.addButton(withTitle: NSLocalizedString(
"Reset App",
comment: "macOS alert button on attempt to reset app"))
alert.addButton(withTitle: NSLocalizedString(
"Cancel", comment: "button title"))

if let window = NSApp.windows.first {
alert.beginSheetModal(for: window) { result in
if case .alertFirstButtonReturn = result {
firstly {
environment.connectionService.disableVPN()
}.map {
AppDataRemover.removeAllData(persistenceService: self.environment?.persistenceService)
self.environment?.navigationController?.popToRoot()
self.mainViewController?.pushSearchOrAddVCIfNoEntries()
self.setShowInStatusBarEnabled(
UserDefaults.standard.showInStatusBar,
shouldUseColorIcons: UserDefaults.standard.isStatusItemInColor)
self.setShowInDockEnabled(UserDefaults.standard.showInDock)
self.setLaunchAtLoginEnabled(UserDefaults.standard.launchAtLogin)
}.cauterize()
}
}
}
}

func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
guard let connectionService = environment?.connectionService else {
return .terminateNow
Expand Down
6 changes: 6 additions & 0 deletions EduVPN/Controllers/Mac/PreferencesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ class PreferencesViewController: ViewController, ParametrizedViewController {
}
}

@IBAction func resetAppClicked(_ sender: Any) {
guard let appDelegate = NSApp.delegate as? AppDelegate else { return }
self.presentingViewController?.dismiss(self)
appDelegate.resetAppAfterConfirming()
}

@IBAction func doneClicked(_ sender: Any) {
self.presentingViewController?.dismiss(self)
}
Expand Down
7 changes: 5 additions & 2 deletions EduVPN/Controllers/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ extension MainViewController {
break
}
viewModel.update()
pushSearchOrAddVCIfNoEntries()
}

func pushSearchOrAddVCIfNoEntries() {
if !environment.persistenceService.hasServers {
if Config.shared.apiDiscoveryEnabled ?? false {
let searchVC = environment.instantiateSearchViewController(
Expand All @@ -386,10 +390,9 @@ extension MainViewController {
predefinedProvider: Config.shared.predefinedProvider,
shouldAutoFocusURLField: false)
addServerVC.delegate = self
environment.navigationController?.pushViewController(addServerVC, animated: true)
environment.navigationController?.pushViewController(addServerVC, animated: false)
}
}

}
}

Expand Down
49 changes: 49 additions & 0 deletions EduVPN/Helpers/Mac/AppDataRemover.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// AppDataRemover.swift
// EduVPN
//

import Foundation

class AppDataRemover {
static func removeAllData(persistenceService: PersistenceService?) {
persistenceService?.removeAllData()
removeLegacyData()
clearCaches()
resetPreferences()
}

static func clearCaches() {
DiscoveryDataFetcher.diskCache.removeAllCachedResponses()
URLCache.shared.removeAllCachedResponses()
}

static func resetPreferences() {
UserDefaults.standard.clearPreferences()
}

static func removeLegacyData() {
let coreDataDbURL = NSPersistentContainer.defaultDirectoryURL()
removeContentsOfDirectory(at: coreDataDbURL, where: { url in
url.isFileURL && url.pathExtension.starts(with: "sqlite")
})

if let appSupportURL = FileHelper.applicationSupportDirectoryUrl() {
removeContentsOfDirectory(at: appSupportURL, where: { url in
url.lastPathComponent != "AddedServers"
})
}
}

private static func removeContentsOfDirectory(at directoryURL: URL, where condition: (URL) -> Bool) {
let fileManager = FileManager.default
let enumerator = fileManager.enumerator(
at: directoryURL, includingPropertiesForKeys: nil,
options: [.skipsSubdirectoryDescendants])
while let url = enumerator?.nextObject() as? URL {
if condition(url) {
try? fileManager.removeItem(at: url)
}
}
}
}
19 changes: 19 additions & 0 deletions EduVPN/Helpers/UserDefaults+Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ extension UserDefaults {
private static let launchAtLoginKey = "launchAtLogin"
#endif

func clearPreferences() {
var keys = [
Self.forceTCPDefaultsKey,
Self.shouldNotifyBeforeSessionExpiryKey,
Self.hasAskedUserOnNotifyBeforeSessionExpiryKey
]
#if os(macOS)
keys.append(contentsOf: [
Self.showInStatusBarKey,
Self.isStatusItemInColorKey,
Self.showInDockKey,
Self.launchAtLoginKey
])
#endif
for key in keys {
removeObject(forKey: key)
}
}

var forceTCP: Bool {
get {
return bool(forKey: Self.forceTCPDefaultsKey)
Expand Down
Loading