-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #104 from wakatime/main
Release v1.2.0
- Loading branch information
Showing
16 changed files
with
330 additions
and
220 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import Foundation | ||
|
||
extension Optional where Wrapped: Collection { | ||
var isEmpty: Bool { | ||
self?.isEmpty ?? true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Foundation | ||
|
||
extension Process { | ||
// Runs process.launch() prior to macOS 13 or process.run() on macOS 13 or newer. | ||
// Adds Swift exception handling to process.launch(). | ||
func execute() throws { | ||
if #available(macOS 13.0, *) { | ||
// Use Process.run() on macOS 13 or newer. Process.run() throws Swift exceptions. | ||
try self.run() | ||
} else { | ||
// Note: Process.launch() can throw ObjC exceptions. For further reference, see | ||
// https://developer.apple.com/documentation/foundation/process/1414189-launch?changes=_3 | ||
try ObjC.catchException { self.launch() } | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import Foundation | ||
|
||
extension URL { | ||
// Implements foward-compat for macOS 13's URL.formatted | ||
func formatted() -> String { | ||
let components = URLComponents(url: self, resolvingAgainstBaseURL: true) | ||
let path = components?.path ?? "" | ||
return path.replacingOccurrences(of: " ", with: "\\ ") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import AppKit | ||
|
||
class Accessibility { | ||
public static func requestA11yPermission() { | ||
let prompt = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String | ||
let options: NSDictionary = [prompt: true] | ||
let appHasPermission = AXIsProcessTrustedWithOptions(options) | ||
if appHasPermission { | ||
// print("has a11y permission") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import Foundation | ||
|
||
// swiftlint:disable force_unwrapping | ||
// swiftlint:disable force_try | ||
class Dependencies { | ||
public static func installDependencies() { | ||
Task { | ||
if !(await isCLILatest()) { | ||
downloadCLI() | ||
} | ||
} | ||
} | ||
|
||
private static func getLatestVersion() async throws -> String? { | ||
struct Release: Decodable { | ||
let tagName: String | ||
private enum CodingKeys: String, CodingKey { | ||
case tagName = "tag_name" | ||
} | ||
} | ||
|
||
let apiUrl = "https://api.github.com/repos/wakatime/wakatime-cli/releases/latest" | ||
var request = URLRequest(url: URL(string: apiUrl)!, cachePolicy: .reloadIgnoringCacheData) | ||
let lastModified = ConfigFile.getSetting(section: "internal", key: "cli_version_last_modified", internalConfig: true) | ||
let currentVersion = ConfigFile.getSetting(section: "internal", key: "cli_version", internalConfig: true) | ||
if let lastModified, currentVersion != nil { | ||
request.setValue(lastModified, forHTTPHeaderField: "If-Modified-Since") | ||
} | ||
let (data, response) = try await URLSession.shared.data(for: request) | ||
guard let httpResponse = response as? HTTPURLResponse else { return nil } | ||
|
||
if httpResponse.statusCode == 304 { | ||
// Current version is still the latest version available | ||
return currentVersion | ||
} else if let lastModified = httpResponse.value(forHTTPHeaderField: "Last-Modified"), | ||
let release = try? JSONDecoder().decode(Release.self, from: data) { | ||
// Remote version successfully decoded | ||
ConfigFile.setSetting(section: "internal", key: "cli_version_last_modified", val: lastModified, internalConfig: true) | ||
ConfigFile.setSetting(section: "internal", key: "cli_version", val: release.tagName, internalConfig: true) | ||
return release.tagName | ||
} else { | ||
// Unexpected response | ||
return nil | ||
} | ||
} | ||
|
||
private static func isCLILatest() async -> Bool { | ||
let cli = NSString.path( | ||
withComponents: ConfigFile.resourcesFolder + ["wakatime-cli"] | ||
) | ||
guard FileManager.default.fileExists(atPath: cli) else { return false } | ||
|
||
let outputPipe = Pipe() | ||
let process = Process() | ||
process.launchPath = cli | ||
process.arguments = ["--version"] | ||
process.standardOutput = outputPipe | ||
process.standardError = FileHandle.nullDevice | ||
do { | ||
try process.run() | ||
} catch { | ||
// Error running CLI process | ||
return false | ||
} | ||
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() | ||
let output = String(decoding: outputData, as: UTF8.self) | ||
let version: String? | ||
if let regex = try? NSRegularExpression(pattern: "([0-9]+\\.[0-9]+\\.[0-9]+)"), | ||
let match = regex.firstMatch(in: output, range: NSRange(output.startIndex..., in: output)), | ||
let range = Range(match.range, in: output) { | ||
version = String(output[range]) | ||
} else { | ||
version = nil | ||
} | ||
let remoteVersion = try? await getLatestVersion() | ||
guard let remoteVersion else { | ||
// Could not retrieve remote version | ||
return true | ||
} | ||
if let version, "v" + version == remoteVersion { | ||
// Local version up to date | ||
return true | ||
} else { | ||
// Newer version available | ||
return false | ||
} | ||
} | ||
|
||
private static func downloadCLI() { | ||
let dir = NSString.path(withComponents: ConfigFile.resourcesFolder) | ||
if !FileManager.default.fileExists(atPath: dir) { | ||
do { | ||
try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true, attributes: nil) | ||
} catch { | ||
print(error.localizedDescription) | ||
} | ||
} | ||
|
||
let url = "https://github.com/wakatime/wakatime-cli/releases/latest/download/wakatime-cli-darwin-\(architecture()).zip" | ||
let zipFile = NSString.path(withComponents: ConfigFile.resourcesFolder + ["wakatime-cli.zip"]) | ||
let cli = NSString.path(withComponents: ConfigFile.resourcesFolder + ["wakatime-cli"]) | ||
let cliReal = NSString.path(withComponents: ConfigFile.resourcesFolder + ["wakatime-cli-darwin-\(architecture())"]) | ||
|
||
if FileManager.default.fileExists(atPath: zipFile) { | ||
do { | ||
try FileManager.default.removeItem(atPath: zipFile) | ||
} catch { | ||
print(error.localizedDescription) | ||
return | ||
} | ||
} | ||
|
||
URLSession.shared.downloadTask(with: URLRequest(url: URL(string: url)!)) { fileUrl, _, _ in | ||
guard let fileUrl else { return } | ||
|
||
do { | ||
// download wakatime-cli.zip | ||
try FileManager.default.moveItem(at: fileUrl, to: URL(fileURLWithPath: zipFile)) | ||
|
||
if FileManager.default.fileExists(atPath: cliReal) { | ||
do { | ||
try FileManager.default.removeItem(atPath: cliReal) | ||
} catch { | ||
print(error.localizedDescription) | ||
return | ||
} | ||
} | ||
|
||
// unzip wakatime-cli.zip | ||
let process = Process() | ||
process.launchPath = "/usr/bin/unzip" | ||
process.arguments = [zipFile, "-d", dir] | ||
process.standardOutput = FileHandle.nullDevice | ||
process.standardError = FileHandle.nullDevice | ||
process.launch() | ||
process.waitUntilExit() | ||
|
||
// cleanup wakatime-cli.zip | ||
try! FileManager.default.removeItem(atPath: zipFile) | ||
|
||
// create ~/.wakatime/wakatime-cli symlink | ||
do { | ||
try FileManager.default.removeItem(atPath: cli) | ||
} catch { } | ||
try! FileManager.default.createSymbolicLink(atPath: cli, withDestinationPath: cliReal) | ||
|
||
} catch { | ||
print(error.localizedDescription) | ||
} | ||
}.resume() | ||
} | ||
|
||
private static func architecture() -> String { | ||
var systeminfo = utsname() | ||
uname(&systeminfo) | ||
let machine = withUnsafeBytes(of: &systeminfo.machine) {bufPtr -> String in | ||
let data = Data(bufPtr) | ||
if let lastIndex = data.lastIndex(where: { $0 != 0 }) { | ||
return String(data: data[0...lastIndex], encoding: .isoLatin1)! | ||
} else { | ||
return String(data: data, encoding: .isoLatin1)! | ||
} | ||
} | ||
if machine == "x86_64" { | ||
return "amd64" | ||
} | ||
return "arm64" | ||
} | ||
} | ||
// swiftlint:enable force_unwrapping | ||
// swiftlint:enable force_try |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import Foundation | ||
|
||
@propertyWrapper | ||
struct Atomic<Value> { | ||
private var value: Value | ||
private let lock = NSLock() | ||
|
||
init(wrappedValue value: Value) { | ||
self.value = value | ||
} | ||
|
||
var wrappedValue: Value { | ||
get { getValue() } | ||
set { setValue(newValue) } | ||
} | ||
|
||
func getValue() -> Value { | ||
lock.lock() | ||
defer { lock.unlock() } | ||
return value | ||
} | ||
|
||
mutating func setValue(_ newValue: Value) { | ||
lock.lock() | ||
defer { lock.unlock() } | ||
value = newValue | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#ifndef ObjC_h | ||
#define ObjC_h | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
@interface ObjC : NSObject | ||
|
||
+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error; | ||
|
||
@end | ||
|
||
#endif /* ObjC_h */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
#import <Foundation/Foundation.h> | ||
|
||
#import "ObjC.h" | ||
|
||
@implementation ObjC | ||
|
||
+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error { | ||
@try { | ||
tryBlock(); | ||
return YES; | ||
} | ||
@catch (NSException *exception) { | ||
*error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo]; | ||
return NO; | ||
} | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// | ||
// Use this file to import your target's public headers that you would like to expose to Swift. | ||
// | ||
|
||
#import "Utils/ObjC.h" |
Oops, something went wrong.