diff --git a/AUTHORS b/AUTHORS index ad116f65..b6d8fe4b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,7 +20,6 @@ $ Contributors and volunteers of the upstream repo, having no responsibility in - Notifier window and Tooltip UI. - App-style installer (only preserved for developer purposes). - mgrPrefs (userdefaults manager). - - apiUpdate. - Mengjuei Hsieh: - McBopomofo for macOS 1.x main developer and architect. - The original C++ version of the User Override Module. diff --git a/Source/3rdParty/OVUpdateAPI/apiUpdate.swift b/Source/3rdParty/OVUpdateAPI/apiUpdate.swift deleted file mode 100644 index e5dd2a41..00000000 --- a/Source/3rdParty/OVUpdateAPI/apiUpdate.swift +++ /dev/null @@ -1,247 +0,0 @@ -// (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -// ==================== -// This code is released under the MIT license (SPDX-License-Identifier: MIT) -// ... with NTL restriction stating that: -// No trademark license is granted to use the trade names, trademarks, service -// marks, or product names of Contributor, except as required to fulfill notice -// requirements defined in MIT License. - -import Cocoa - -enum VersionUpdateApi { - static let kCheckUpdateAutomatically = UserDef.kCheckUpdateAutomatically.rawValue - static let kNextUpdateCheckDateKey = "NextUpdateCheckDate" - static let kUpdateInfoEndpointKey = "UpdateInfoEndpoint" - static let kUpdateInfoSiteKey = "UpdateInfoSite" - static let kVersionDescription = "VersionDescription" - static let kNextCheckInterval: TimeInterval = 86400.0 - static let kTimeoutInterval: TimeInterval = 60.0 - static func check( - forced: Bool, callback: @escaping (Result) -> Void - ) -> URLSessionTask? { - guard let infoDict = Bundle.main.infoDictionary, - let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, - let updateInfoURL = URL(string: updateInfoURLString) - else { - return nil - } - - let request = URLRequest( - url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, - timeoutInterval: kTimeoutInterval - ) - let task = URLSession.shared.dataTask(with: request) { data, _, error in - if let error = error { - DispatchQueue.main.async { - forced - ? callback( - .failure( - VersionUpdateApiError.connectionError( - message: error.localizedDescription))) - : callback(.success(.ignored)) - } - return - } - - do { - guard - let plist = try PropertyListSerialization.propertyList( - from: data ?? Data(), options: [], format: nil - ) as? [AnyHashable: Any], - let remoteVersion = plist[kCFBundleVersionKey] as? String, - let infoDict = Bundle.main.infoDictionary - else { - DispatchQueue.main.async { - forced - ? callback(.success(.noNeedToUpdate)) - : callback(.success(.ignored)) - } - return - } - - // TODO: Validate info (e.g. bundle identifier) - // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this - - let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" - let result = currentVersion.compare( - remoteVersion, options: .numeric, range: nil, locale: nil - ) - - if result != .orderedAscending { - DispatchQueue.main.async { - forced - ? callback(.success(.noNeedToUpdate)) - : callback(.success(.ignored)) - } - IME.prtDebugIntel( - "vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available." - ) - return - } - IME.prtDebugIntel( - "vChewingDebug: Update // New version detected, proceeding to the next phase.") - guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, - let siteInfoURL = URL(string: siteInfoURLString) - else { - DispatchQueue.main.async { - forced - ? callback(.success(.noNeedToUpdate)) - : callback(.success(.ignored)) - } - IME.prtDebugIntel( - "vChewingDebug: Update // Failed from retrieving / parsing URL intel.") - return - } - IME.prtDebugIntel( - "vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.") - var report = VersionUpdateReport(siteUrl: siteInfoURL) - var versionDescription = "" - let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any] - if let versionDescriptions = versionDescriptions { - var locale = "en" - let preferredTags = Bundle.preferredLocalizations(from: IME.arrSupportedLocales) - if let first = preferredTags.first { - locale = first - } - versionDescription = - versionDescriptions[locale] as? String ?? versionDescriptions["en"] - as? String ?? "" - if !versionDescription.isEmpty { - versionDescription = "\n\n" + versionDescription - } - } - report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? "" - report.currentVersion = currentVersion - report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? "" - report.remoteVersion = remoteVersion - report.versionDescription = versionDescription - DispatchQueue.main.async { - callback(.success(.shouldUpdate(report: report))) - } - IME.prtDebugIntel("vChewingDebug: Update // Callbck Complete.") - } catch { - DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) - } - } - } - task.resume() - return task - } - - private static var checkTask: URLSessionTask? - static func checkForUpdate(forced: Bool = false) { - if checkTask != nil { - // busy - return - } - - // time for update? - if !forced { - if !mgrPrefs.checkUpdateAutomatically { - return - } - let now = Date() - let date = UserDefaults.standard.object(forKey: VersionUpdateApi.kNextUpdateCheckDateKey) as? Date ?? now - if now.compare(date) == .orderedAscending { - return - } - } - - let nextUpdateDate = Date(timeInterval: VersionUpdateApi.kNextCheckInterval, since: Date()) - UserDefaults.standard.set(nextUpdateDate, forKey: VersionUpdateApi.kNextUpdateCheckDateKey) - - checkTask = VersionUpdateApi.check(forced: forced) { [self] result in - defer { - checkTask = nil - } - switch result { - case .success(let apiResult): - switch apiResult { - case .shouldUpdate(let report): - let content = String( - format: NSLocalizedString( - "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", - comment: "" - ), - report.currentShortVersion, - report.currentVersion, - report.remoteShortVersion, - report.remoteVersion, - report.versionDescription - ) - IME.prtDebugIntel("vChewingDebug: \(content)") - let alert = NSAlert() - alert.messageText = NSLocalizedString("New Version Available", comment: "") - alert.informativeText = content - alert.addButton(withTitle: NSLocalizedString("Visit Website", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Not Now", comment: "")) - NSApp.setActivationPolicy(.accessory) - let result = alert.runModal() - if result == NSApplication.ModalResponse.alertFirstButtonReturn { - if let siteURL = report.siteUrl { - NSWorkspace.shared.open(siteURL) - } - } - case .noNeedToUpdate, .ignored: - break - } - case .failure(let error): - switch error { - case VersionUpdateApiError.connectionError(let message): - let title = NSLocalizedString("Update Check Failed", comment: "") - let content = String( - format: NSLocalizedString( - "There may be no internet connection or the server failed to respond.\n\nError message: %@", - comment: "" - ), message - ) - let buttonTitle = NSLocalizedString("Dismiss", comment: "") - IME.prtDebugIntel("vChewingDebug: \(content)") - - let alert = NSAlert() - alert.messageText = title - alert.informativeText = content - alert.addButton(withTitle: buttonTitle) - alert.runModal() - NSApp.setActivationPolicy(.accessory) - default: - break - } - } - } - } - - struct VersionUpdateReport { - var siteUrl: URL? - var currentShortVersion: String = "" - var currentVersion: String = "" - var remoteShortVersion: String = "" - var remoteVersion: String = "" - var versionDescription: String = "" - } - - enum VersionUpdateApiResult { - case shouldUpdate(report: VersionUpdateReport) - case noNeedToUpdate - case ignored - } - - enum VersionUpdateApiError: Error, LocalizedError { - case connectionError(message: String) - - var errorDescription: String? { - switch self { - case .connectionError(let message): - return String( - format: NSLocalizedString( - "There may be no internet connection or the server failed to respond.\n\nError message: %@", - comment: "" - ), message - ) - } - } - } -} diff --git a/Source/Modules/AppDelegate.swift b/Source/Modules/AppDelegate.swift index 96eb3585..154c0fa9 100644 --- a/Source/Modules/AppDelegate.swift +++ b/Source/Modules/AppDelegate.swift @@ -65,7 +65,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele // 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。 if mgrPrefs.checkUpdateAutomatically { - VersionUpdateApi.checkForUpdate() + UpdateSputnik.shared.checkForUpdate() } } diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift index 2c9e398e..8b975c43 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift @@ -135,7 +135,7 @@ class ctlInputMethod: IMKInputController { setKeyLayout() handle(state: IMEState.ofEmpty()) } // 除此之外就不要動了,免得在點開輸入法自身的視窗時卡死。 - VersionUpdateApi.checkForUpdate() + UpdateSputnik.shared.checkForUpdate() } /// 停用輸入法時,會觸發該函式。 diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift b/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift index c1ab11bb..31b35756 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift @@ -308,7 +308,7 @@ extension ctlInputMethod { } @objc func checkForUpdate(_: Any?) { - VersionUpdateApi.checkForUpdate(forced: true) + UpdateSputnik.shared.checkForUpdate(forced: true) } @objc func openUserDataFolder(_: Any?) { diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 430928b3..cbff1ea5 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -93,7 +93,6 @@ 5BC447A02865686500EDC323 /* data-symbols.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71E283B4AEA0078EB25 /* data-symbols.plist */; }; 5BC447A12865686500EDC323 /* data-zhuyinwen.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71F283B4AEA0078EB25 /* data-zhuyinwen.plist */; }; 5BC447A628656A1900EDC323 /* PrefManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A228656A1900EDC323 /* PrefManagerTests.swift */; }; - 5BC447A928656A1900EDC323 /* UpdateAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A528656A1900EDC323 /* UpdateAPITests.swift */; }; 5BC447AE2865CFC200EDC323 /* KeyHandlerTestsNormalCHS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */; }; 5BC6E03C286692BE00291771 /* KeyHandlerTestsSCPCCHT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */; }; 5BD0113B28180D6100609769 /* LMInstantiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0113A28180D6100609769 /* LMInstantiator.swift */; }; @@ -105,7 +104,6 @@ 5BD05C6827B2BBEF004C4F1D /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6327B2BBEF004C4F1D /* Content.swift */; }; 5BD05C6927B2BBEF004C4F1D /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6427B2BBEF004C4F1D /* WindowController.swift */; }; 5BD05C6A27B2BBEF004C4F1D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6527B2BBEF004C4F1D /* ViewController.swift */; }; - 5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */; }; 5BDCBB2E27B4E67A00D0CC59 /* vChewingPhraseEditor.app in Resources */ = {isa = PBXBuildFile; fileRef = 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */; }; 5BE377A0288FED8D0037365B /* KeyHandler_HandleComposition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE3779F288FED8D0037365B /* KeyHandler_HandleComposition.swift */; }; 5BE78BD927B3775B005EA1BE /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */; }; @@ -312,7 +310,6 @@ 5BC447A228656A1900EDC323 /* PrefManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefManagerTests.swift; sourceTree = ""; }; 5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerTestsSCPCCHT.swift; sourceTree = ""; }; 5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerTestsNormalCHS.swift; sourceTree = ""; }; - 5BC447A528656A1900EDC323 /* UpdateAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAPITests.swift; sourceTree = ""; }; 5BC447AB2865BEF500EDC323 /* AUTHORS */ = {isa = PBXFileReference; lastKnownFileType = text; path = AUTHORS; sourceTree = ""; }; 5BD0113A28180D6100609769 /* LMInstantiator.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = LMInstantiator.swift; sourceTree = ""; usesTabs = 0; }; 5BD0113C2818543900609769 /* KeyHandler_Core.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = KeyHandler_Core.swift; sourceTree = ""; usesTabs = 0; }; @@ -324,7 +321,6 @@ 5BD05C6327B2BBEF004C4F1D /* Content.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = Content.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BD05C6427B2BBEF004C4F1D /* WindowController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = WindowController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BD05C6527B2BBEF004C4F1D /* ViewController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ViewController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; - 5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = apiUpdate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BDCBB4227B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = ""; }; 5BDCBB4327B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = ""; }; 5BDCBB4527B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmPrefWindow.strings"; sourceTree = ""; }; @@ -425,14 +421,6 @@ path = FolderMonitor; sourceTree = ""; }; - 5B16B84D28C9D6BF00ABA692 /* OVUpdateAPI */ = { - isa = PBXGroup; - children = ( - 5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */, - ); - path = OVUpdateAPI; - sourceTree = ""; - }; 5B18BA7527C7BF6D0056EB19 /* MiscRootFiles */ = { isa = PBXGroup; children = ( @@ -465,7 +453,6 @@ 5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */, 5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */, 5BC447A228656A1900EDC323 /* PrefManagerTests.swift */, - 5BC447A528656A1900EDC323 /* UpdateAPITests.swift */, ); path = vChewingTests; sourceTree = ""; @@ -498,7 +485,6 @@ 5B84579B2871AD2200C93B01 /* HotenkaChineseConverter */, 5B949BD72816DC4400D87B5D /* LineReader */, 5B5F8AEC28C86AB3007C11F1 /* NSAttributedTextView */, - 5B16B84D28C9D6BF00ABA692 /* OVUpdateAPI */, 5BA58644289BCFAC0077D02F /* Qwertyyb */, 5B20430528BEE2F300BFC6FD /* Sandbox */, 5BA9FCEA27FED652002DE248 /* SindreSorhus */, @@ -1191,7 +1177,6 @@ files = ( 5BC447A628656A1900EDC323 /* PrefManagerTests.swift in Sources */, 5BC6E03C286692BE00291771 /* KeyHandlerTestsSCPCCHT.swift in Sources */, - 5BC447A928656A1900EDC323 /* UpdateAPITests.swift in Sources */, 5BC447AE2865CFC200EDC323 /* KeyHandlerTestsNormalCHS.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1291,7 +1276,6 @@ 5BA9FD3F27FEF3C8002DE248 /* Pane.swift in Sources */, 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */, 5BE377A0288FED8D0037365B /* KeyHandler_HandleComposition.swift in Sources */, - 5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/vChewingTests/UpdateAPITests.swift b/vChewingTests/UpdateAPITests.swift deleted file mode 100644 index cbd8b59e..00000000 --- a/vChewingTests/UpdateAPITests.swift +++ /dev/null @@ -1,29 +0,0 @@ -// (c) 2021 and onwards Zonble Yang (MIT-NTL License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -// ==================== -// This code is released under the MIT license (SPDX-License-Identifier: MIT) -// ... with NTL restriction stating that: -// No trademark license is granted to use the trade names, trademarks, service -// marks, or product names of Contributor, except as required to fulfill notice -// requirements defined in MIT License. - -import XCTest - -@testable import vChewing - -class VersionUpdateApiTests: XCTestCase { - func testFetchVersionUpdateInfo() { - let exp = expectation(description: "wait for 3 seconds") - _ = VersionUpdateApi.check(forced: true) { result in - exp.fulfill() - switch result { - case .success: - break - case .failure(let error): - XCTFail(error.localizedDescription) - } - } - wait(for: [exp], timeout: 20.0) - } -}