11import AppKit
2+ import FeedKit
23import Foundation
34import Logger
45import SwiftUI
56
6- struct Release : Codable {
7- let tag_name : String ?
8- let html_url : String ?
9- let body : String ?
10- let published_at : String ?
11- }
12-
137let skippedUpdateVersionKey = " skippedUpdateVersion "
148
159public struct UpdateChecker {
@@ -19,94 +13,85 @@ public struct UpdateChecker {
1913
2014 public init ( ) { }
2115
22- public func checkForUpdate( ) async {
23- let url =
24- URL ( string: " https://api.github.com/repos/intitni/CopilotForXcode/releases/latest " ) !
25- do {
26- var request = URLRequest ( url: url)
27- let token = ( Bundle . main. infoDictionary ? [ " GITHUB_TOKEN " ] as? String ) ?? " "
28- if !token. isEmpty {
29- request. setValue ( " Bearer \( token) " , forHTTPHeaderField: " Authorization " )
16+ public func checkForUpdate( ) {
17+ let url = URL ( string: " https://github.com/intitni/CopilotForXcode/releases.atom " ) !
18+ let parser = FeedParser ( URL: url)
19+ parser. parseAsync { result in
20+ switch result {
21+ case let . success( . atom( feed) ) :
22+ if let entry = feed. entries? . first ( where: {
23+ guard let title = $0. title else { return false }
24+ return !title. contains ( " - " )
25+ } ) {
26+ self . alertIfUpdateAvailable ( entry)
27+ }
28+ case let . failure( error) :
29+ Logger . updateChecker. error ( error)
30+ default : break
3031 }
31- request. setValue ( " application/vnd.github+json " , forHTTPHeaderField: " Accept " )
32- request. setValue ( " X-GitHub-Api-Version " , forHTTPHeaderField: " 2022-11-28 " )
33-
34- let ( data, _) = try await URLSession . shared. data ( for: request)
35- let decoder = JSONDecoder ( )
36- let release = try decoder. decode ( Release . self, from: data)
37- guard let version = release. tag_name,
38- version != skippedUpdateVersion,
39- version != Bundle . main
40- . infoDictionary ? [ " CFBundleShortVersionString " ] as? String
41- else { return }
32+ }
33+ }
4234
43- Task { @MainActor in
44- let alert = NSAlert ( )
45- alert. messageText = " Copilot for Xcode \( version) is available! "
46- alert. informativeText = " Would you like to visit the release page? "
47- let view = NSHostingView (
48- rootView:
49- AccessoryView ( releaseNote: release. body)
50- )
51- view. frame = . init( origin: . zero, size: . init( width: 400 , height: 200 ) )
52- alert. accessoryView = view
35+ func alertIfUpdateAvailable( _ entry: AtomFeedEntry ) {
36+ guard let version = entry. title,
37+ let currentVersion = Bundle . main
38+ . infoDictionary ? [ " CFBundleShortVersionString " ] as? String ,
39+ version != skippedUpdateVersion,
40+ version. compare ( currentVersion, options: . numeric) == . orderedDescending
41+ else { return }
5342
54- alert. addButton ( withTitle: " Visit Release Page " )
55- alert. addButton ( withTitle: " Skip This Version " )
56- alert. addButton ( withTitle: " Cancel " )
57- alert. alertStyle = . informational
58- let screenFrame = NSScreen . main? . frame ?? . zero
59- let window = NSWindow (
60- contentRect: . init(
61- x: screenFrame. midX,
62- y: screenFrame. midY,
63- width: 1 ,
64- height: 1
65- ) ,
66- styleMask: . borderless,
67- backing: . buffered,
68- defer: true
69- )
70- window. level = . floating
71- window. isReleasedWhenClosed = false
72- alert. beginSheetModal ( for: window) { [ window] response in
73- switch response {
74- case . alertFirstButtonReturn:
75- if let url = URL ( string: release. html_url ?? " " ) {
76- NSWorkspace . shared. open ( url)
77- }
78- case . alertSecondButtonReturn:
79- UserDefaults . standard. set ( version, forKey: skippedUpdateVersionKey)
80- default :
81- break
82- }
83- window. close ( )
84- }
85- }
86- } catch {
87- Logger . updateChecker. error ( error)
43+ Task { @MainActor in
44+ let screenFrame = NSScreen . main? . frame ?? . zero
45+ let window = NSWindow (
46+ contentRect: . init(
47+ x: screenFrame. midX,
48+ y: screenFrame. midY + 200 ,
49+ width: 500 ,
50+ height: 500
51+ ) ,
52+ styleMask: . borderless,
53+ backing: . buffered,
54+ defer: true
55+ )
56+ window. level = . floating
57+ window. isReleasedWhenClosed = false
58+ window. contentViewController = NSHostingController (
59+ rootView: AlertView ( entry: entry, window: window)
60+ )
61+ window. makeKeyAndOrderFront ( nil )
8862 }
8963 }
9064}
9165
92- struct AccessoryView : View {
93- let releaseNote : String ?
66+ struct AlertView : View {
67+ let entry : AtomFeedEntry
68+ let window : NSWindow
9469
9570 var body : some View {
96- if let releaseNote {
97- ScrollView {
98- Text ( releaseNote)
99- . padding ( )
100-
101- Spacer ( )
71+ let version = entry. title ?? " 0.0.0 "
72+ Color . clear. alert (
73+ " Copilot for Xcode \( version) is available! " ,
74+ isPresented: . constant( true )
75+ ) {
76+ Button {
77+ if let url = URL ( string: entry. links? . first? . attributes? . href ?? " " ) {
78+ NSWorkspace . shared. open ( url)
79+ }
80+ window. close ( )
81+ } label: {
82+ Text ( " Visit Release Page " )
10283 }
103- . background ( Color ( nsColor: . textBackgroundColor) )
104- . overlay {
105- Rectangle ( )
106- . stroke ( Color ( nsColor: . separatorColor) , style: . init( lineWidth: 2 ) )
84+
85+ Button {
86+ UserDefaults . standard. set ( version, forKey: skippedUpdateVersionKey)
87+ window. close ( )
88+ } label: {
89+ Text ( " Skip This Version " )
10790 }
108- } else {
109- EmptyView ( )
91+
92+ Button { window. close ( ) } label: { Text ( " Cancel " ) }
93+ } message: {
94+ Text ( " Would you like to visit the release page? " )
11095 }
11196 }
11297}
0 commit comments