Add CI/CD configuration and API documentation
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import Foundation
|
||||
import Library
|
||||
|
||||
public enum ProfileUpdateTask {
|
||||
static let minUpdateInterval: TimeInterval = 15 * 60
|
||||
static let defaultUpdateInterval: TimeInterval = 60 * 60
|
||||
|
||||
private static var timer: Timer?
|
||||
|
||||
public static func configure() async throws {
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
let profiles = try await ProfileManager.listAutoUpdateEnabled()
|
||||
if profiles.isEmpty {
|
||||
return
|
||||
}
|
||||
var updateInterval = profiles.map { it in
|
||||
it.autoUpdateIntervalOrDefault
|
||||
}.min()!
|
||||
if updateInterval < minUpdateInterval {
|
||||
updateInterval = minUpdateInterval
|
||||
}
|
||||
timer = Timer(fire: calculateEarliestBeginDate(profiles), interval: updateInterval, repeats: true) { _ in
|
||||
Task {
|
||||
await getAndupdateProfiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func calculateEarliestBeginDate(_ profiles: [Profile]) -> Date {
|
||||
let nowTime = Date.now
|
||||
var earliestBeginDate = profiles.map { it in
|
||||
it.lastUpdated!.addingTimeInterval(it.autoUpdateIntervalOrDefault)
|
||||
}.min()!
|
||||
if earliestBeginDate <= nowTime {
|
||||
earliestBeginDate = nowTime
|
||||
}
|
||||
return earliestBeginDate
|
||||
}
|
||||
|
||||
private nonisolated static func getAndupdateProfiles() async {
|
||||
do {
|
||||
_ = try await updateProfiles(ProfileManager.listAutoUpdateEnabled())
|
||||
NSLog("profile update task succeed")
|
||||
} catch {
|
||||
NSLog("profile update task failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
static func updateProfiles(_ profiles: [Profile]) async -> Bool {
|
||||
var success = true
|
||||
for profile in profiles {
|
||||
if profile.lastUpdated! > Date(timeIntervalSinceNow: -profile.autoUpdateIntervalOrDefault) {
|
||||
continue
|
||||
}
|
||||
do {
|
||||
try await profile.updateRemoteProfile()
|
||||
NSLog("Updated profile \(profile.name)")
|
||||
} catch {
|
||||
NSLog("Update profile \(profile.name) failed: \(error.localizedDescription)")
|
||||
success = false
|
||||
}
|
||||
}
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
extension Profile {
|
||||
var autoUpdateIntervalOrDefault: TimeInterval {
|
||||
if autoUpdateInterval > 0 {
|
||||
return TimeInterval(autoUpdateInterval * 60)
|
||||
} else {
|
||||
return ProfileUpdateTask.defaultUpdateInterval
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// File.swift
|
||||
// LoginKit
|
||||
//
|
||||
// Created by Mac on 2024/10/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Foundation
|
||||
|
||||
//MACOS
|
||||
// 创建一个全局的 UserManager 类,负责从 UserDefaults 中读取和存储用户信息及登录状态
|
||||
class StoreManager {
|
||||
static let shared = StoreManager()
|
||||
|
||||
|
||||
public let configURL = "https://api.gooapis.com/api/vpnconfig.php"
|
||||
|
||||
// UserDefaults keys
|
||||
private let loginStatusKey = "isLoggedIn"
|
||||
private let userEmailKey = "userEmail"
|
||||
private let avatarurlKey = "avatar_url"
|
||||
private let autodataKey = "autodata"
|
||||
private let subURLDataKey = "subURLData"
|
||||
private let baseURLKey = "baseURLKey"
|
||||
|
||||
|
||||
// UserDefaults 引用
|
||||
private let defaults = UserDefaults.standard
|
||||
|
||||
func baseURL()-> String{
|
||||
return UserDefaults.standard.string(forKey: "baseURLKey") ?? ""
|
||||
}
|
||||
|
||||
func mainregisterURL()-> String{
|
||||
UserDefaults.standard.string(forKey: "mainregisterURLKey") ?? ""
|
||||
}
|
||||
|
||||
func paymentURL()-> String{
|
||||
UserDefaults.standard.string(forKey: "paymentURLKey") ?? ""
|
||||
}
|
||||
func telegramUrl()-> String{
|
||||
UserDefaults.standard.string(forKey: "telegramUrlKey") ?? ""
|
||||
}
|
||||
func kefuUrl()-> String{
|
||||
UserDefaults.standard.string(forKey: "kefuUrlKey") ?? ""
|
||||
}
|
||||
func websiteURL()-> String{
|
||||
UserDefaults.standard.string(forKey: "websiteURLKey") ?? ""
|
||||
}
|
||||
|
||||
//存储 URL 相关信息
|
||||
func storebaseURLData(data: String) {
|
||||
defaults.set(data, forKey: "baseURLKey")
|
||||
defaults.synchronize()
|
||||
}
|
||||
|
||||
func storemainregisterURLData(data: String) {
|
||||
defaults.set(data, forKey: "mainregisterURLKey")
|
||||
defaults.synchronize()
|
||||
}
|
||||
|
||||
func storepaymentURLData(data: String) {
|
||||
defaults.set(data, forKey: "paymentURLKey")
|
||||
defaults.synchronize()
|
||||
}
|
||||
|
||||
func storetelegramUrlData(data: String) {
|
||||
defaults.set(data, forKey: "telegramUrlKey")
|
||||
defaults.synchronize()
|
||||
}
|
||||
|
||||
func storekefuUrlData(data: String) {
|
||||
defaults.set(data, forKey: "kefuUrlKey")
|
||||
defaults.synchronize()
|
||||
}
|
||||
func storewebsiteURLData(data: String) {
|
||||
defaults.set(data, forKey: "websiteURLKey")
|
||||
defaults.synchronize()
|
||||
}
|
||||
|
||||
|
||||
// 更新登录状态
|
||||
func updateLoginStatus(_ loggedIn: Bool) {
|
||||
defaults.set(loggedIn, forKey: loginStatusKey)
|
||||
defaults.synchronize()
|
||||
}
|
||||
|
||||
|
||||
// 检查是否已登录
|
||||
func isUserLoggedIn() -> Bool {
|
||||
return defaults.bool(forKey: loginStatusKey)
|
||||
}
|
||||
|
||||
// 存储用户信息
|
||||
func storeUserInfo(email: String, avator: String) {
|
||||
defaults.set(email, forKey: userEmailKey)
|
||||
defaults.set(avator, forKey: avatarurlKey) // 假设 info 是 JSON 字符串
|
||||
defaults.synchronize()
|
||||
}
|
||||
|
||||
// 读取用户信息
|
||||
func getUserInfo() -> (email: String, avator: String) {
|
||||
let email = defaults.string(forKey: userEmailKey) ?? ""
|
||||
let avator = defaults.string(forKey: avatarurlKey) ?? ""
|
||||
return (email, avator)
|
||||
}
|
||||
|
||||
// 存储 autodata
|
||||
func storeAutoData(data: String) {
|
||||
defaults.set(data, forKey: autodataKey)
|
||||
defaults.synchronize()
|
||||
}
|
||||
|
||||
// 读取 autodata
|
||||
func getSuburlData() -> String {
|
||||
return defaults.string(forKey: subURLDataKey) ?? ""
|
||||
}
|
||||
|
||||
// 存储 autodata
|
||||
func storeSuburlData(data: String) {
|
||||
defaults.set(data, forKey: subURLDataKey)
|
||||
defaults.synchronize()
|
||||
}
|
||||
|
||||
// 读取 autodata
|
||||
func getAutoData() -> String {
|
||||
return defaults.string(forKey: autodataKey) ?? ""
|
||||
}
|
||||
|
||||
// 清除所有数据(例如在登出时)
|
||||
func clearUserData() {
|
||||
defaults.removeObject(forKey: loginStatusKey)
|
||||
defaults.removeObject(forKey: userEmailKey)
|
||||
defaults.removeObject(forKey: avatarurlKey)
|
||||
defaults.removeObject(forKey: autodataKey)
|
||||
defaults.removeObject(forKey: subURLDataKey)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import BackgroundTasks
|
||||
import Foundation
|
||||
import Library
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
public class UIProfileUpdateTask: BGAppRefreshTask {
|
||||
private static let taskSchedulerPermittedIdentifier = "\(FilePath.packageName).update_profiles"
|
||||
|
||||
private static var registered = false
|
||||
public static func configure() throws {
|
||||
if !registered {
|
||||
let success = BGTaskScheduler.shared.register(forTaskWithIdentifier: taskSchedulerPermittedIdentifier, using: nil) { task in
|
||||
NSLog("profile update task started")
|
||||
Task {
|
||||
await UIProfileUpdateTask.getAndUpdateProfiles(task)
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
throw NSError(domain: "register task failed", code: 0)
|
||||
}
|
||||
registered = true
|
||||
}
|
||||
Task {
|
||||
BGTaskScheduler.shared.cancelAllTaskRequests()
|
||||
let profiles = try await ProfileManager.listAutoUpdateEnabled()
|
||||
if profiles.isEmpty {
|
||||
return
|
||||
}
|
||||
try scheduleUpdate(ProfileUpdateTask.calculateEarliestBeginDate(profiles))
|
||||
}
|
||||
Task {
|
||||
if await UIApplication.shared.backgroundRefreshStatus != .available {
|
||||
await updateOnce()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private nonisolated static func updateOnce() async {
|
||||
NSLog("update profiles at start since background refresh unavailable")
|
||||
let profiles: [Profile]
|
||||
do {
|
||||
profiles = try await ProfileManager.listAutoUpdateEnabled()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
if profiles.isEmpty {
|
||||
return
|
||||
}
|
||||
_ = await ProfileUpdateTask.updateProfiles(profiles)
|
||||
}
|
||||
|
||||
private nonisolated static func getAndUpdateProfiles(_ task: BGTask) async {
|
||||
let profiles: [Profile]
|
||||
do {
|
||||
profiles = try await ProfileManager.listAutoUpdateEnabled()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
if profiles.isEmpty {
|
||||
return
|
||||
}
|
||||
let success = await ProfileUpdateTask.updateProfiles(profiles)
|
||||
try? scheduleUpdate(ProfileUpdateTask.calculateEarliestBeginDate(profiles))
|
||||
task.setTaskCompleted(success: success)
|
||||
task.expirationHandler = {
|
||||
try? scheduleUpdate(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private static func scheduleUpdate(_ earliestBeginDate: Date?) throws {
|
||||
let request = BGAppRefreshTaskRequest(identifier: taskSchedulerPermittedIdentifier)
|
||||
request.earliestBeginDate = earliestBeginDate
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user