mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
Merge branch 'main' into Notifications
This commit is contained in:
commit
ecfb49a216
11 changed files with 195 additions and 131 deletions
|
|
@ -7,6 +7,8 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
536CFDD2263C94DE00026CE0 /* SignedInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD1263C94DE00026CE0 /* SignedInView.swift */; };
|
||||
536CFDD4263C9A8000026CE0 /* XcodesSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD3263C9A8000026CE0 /* XcodesSheet.swift */; };
|
||||
63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.swift */; };
|
||||
CA11E7BA2598476C00D2EE1C /* XcodeCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */; };
|
||||
CA2518EC25A7FF2B00F08414 /* AppStateUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */; };
|
||||
|
|
@ -158,6 +160,8 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
536CFDD1263C94DE00026CE0 /* SignedInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignedInView.swift; sourceTree = "<group>"; };
|
||||
536CFDD3263C9A8000026CE0 /* XcodesSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodesSheet.swift; sourceTree = "<group>"; };
|
||||
63EAA4EA259944450046AB8F /* ProgressButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButton.swift; sourceTree = "<group>"; };
|
||||
CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeCommands.swift; sourceTree = "<group>"; };
|
||||
CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateUpdateTests.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -311,6 +315,7 @@
|
|||
CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */,
|
||||
63EAA4EA259944450046AB8F /* ProgressButton.swift */,
|
||||
CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */,
|
||||
536CFDD3263C9A8000026CE0 /* XcodesSheet.swift */,
|
||||
);
|
||||
path = Common;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -386,6 +391,7 @@
|
|||
CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */,
|
||||
CAA1CB48255A5C97003FD669 /* SignInSMSView.swift */,
|
||||
CAA1CB4C255A5CFD003FD669 /* SignInPhoneListView.swift */,
|
||||
536CFDD1263C94DE00026CE0 /* SignedInView.swift */,
|
||||
);
|
||||
path = SignIn;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -761,6 +767,7 @@
|
|||
CAFBDC4E2599B33D003DCC5A /* MainToolbar.swift in Sources */,
|
||||
CA11E7BA2598476C00D2EE1C /* XcodeCommands.swift in Sources */,
|
||||
CAA8589B25A2B83000ACF8C0 /* Aria2CError.swift in Sources */,
|
||||
536CFDD2263C94DE00026CE0 /* SignedInView.swift in Sources */,
|
||||
CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */,
|
||||
CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */,
|
||||
CAE4248C259A68B800B8B246 /* Optional+IsNotNil.swift in Sources */,
|
||||
|
|
@ -821,6 +828,7 @@
|
|||
CABFA9CC2592EEEA00380FEE /* Path+.swift in Sources */,
|
||||
CAD2E7A22449574E00113D76 /* XcodesApp.swift in Sources */,
|
||||
63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */,
|
||||
536CFDD4263C9A8000026CE0 /* XcodesSheet.swift in Sources */,
|
||||
E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */,
|
||||
CA5D781E257365D6008EDE9D /* PinCodeTextView.swift in Sources */,
|
||||
CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "CombineExpectations",
|
||||
"repositoryURL": "https://github.com/groue/CombineExpectations",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "989a92221899929ab8347a5878aa2b16db8b81ca",
|
||||
"version": "0.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "XcodeReleases",
|
||||
"repositoryURL": "https://github.com/xcodereleases/data",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "b47228c688b608e34b3b84079ab6052a24c7a981",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "ErrorHandling",
|
||||
"repositoryURL": "https://github.com/RobotsAndPencils/ErrorHandling",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "7be837fcb515447c0776805c3288fb7d5181ec68",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "KeychainAccess",
|
||||
"repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "8d33ffd6f74b3bcfc99af759d4204c6395a3f918",
|
||||
"version": "3.2.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "LegibleError",
|
||||
"repositoryURL": "https://github.com/mxcl/LegibleError",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "909e9bab3ded97350b28a5ab41dd745dd8aa9710",
|
||||
"version": "1.0.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Path.swift",
|
||||
"repositoryURL": "https://github.com/mxcl/Path.swift",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "dac007e907a4f4c565cfdc55a9ce148a761a11d5",
|
||||
"version": "0.16.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Sparkle",
|
||||
"repositoryURL": "https://github.com/sparkle-project/Sparkle/",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "891afd44c7075e699924ed9b81d8dc94a5111dfd",
|
||||
"version": "1.24.0-spm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftSoup",
|
||||
"repositoryURL": "https://github.com/scinfu/SwiftSoup",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "aeb5b4249c273d1783a5299e05be1b26e061ea81",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Version",
|
||||
"repositoryURL": "https://github.com/mxcl/Version",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "087c91fedc110f9f833b14ef4c32745dabca8913",
|
||||
"version": "1.0.3"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -36,6 +36,10 @@ public class Client {
|
|||
case 401:
|
||||
return Fail(error: AuthenticationError.invalidUsernameOrPassword(username: accountName))
|
||||
.eraseToAnyPublisher()
|
||||
case 403:
|
||||
let errorMessage = responseBody.serviceErrors?.first?.description.replacingOccurrences(of: "-20209: ", with: "") ?? ""
|
||||
return Fail(error: AuthenticationError.accountLocked(errorMessage))
|
||||
.eraseToAnyPublisher()
|
||||
case 409:
|
||||
return self.handleTwoStepOrFactor(data: data, response: response, serviceKey: serviceKey)
|
||||
case 412 where Client.authTypes.contains(responseBody.authType ?? ""):
|
||||
|
|
@ -180,6 +184,7 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
|
|||
case appleIDAndPrivacyAcknowledgementRequired
|
||||
case accountUsesTwoStepAuthentication
|
||||
case accountUsesUnknownAuthenticationKind(String?)
|
||||
case accountLocked(String)
|
||||
case badStatusCode(statusCode: Int, data: Data, response: HTTPURLResponse)
|
||||
|
||||
public var errorDescription: String? {
|
||||
|
|
@ -203,6 +208,8 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
|
|||
return "Received a response from Apple that indicates this account has two-step authentication enabled. xcodes currently only supports the newer two-factor authentication, though. Please consider upgrading to two-factor authentication, or explain why this isn't an option for you by making a new feature request in the Help menu."
|
||||
case .accountUsesUnknownAuthenticationKind:
|
||||
return "Received a response from Apple that indicates this account has two-step or two-factor authentication enabled, but xcodes is unsure how to handle this response. If you continue to have problems, please submit a bug report in the Help menu."
|
||||
case let .accountLocked(message):
|
||||
return message
|
||||
case let .badStatusCode(statusCode, _, _):
|
||||
return "Received an unexpected status code: \(statusCode). If you continue to have problems, please submit a bug report in the Help menu."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class AppState: ObservableObject {
|
|||
}
|
||||
@Published var updatePublisher: AnyCancellable?
|
||||
var isUpdating: Bool { updatePublisher != nil }
|
||||
@Published var presentingSignInAlert = false
|
||||
@Published var presentedSheet: XcodesSheet? = nil
|
||||
@Published var isProcessingAuthRequest = false
|
||||
@Published var secondFactorData: SecondFactorData?
|
||||
@Published var xcodeBeingConfirmedForUninstallation: Xcode?
|
||||
|
|
@ -68,6 +68,14 @@ class AppState: ObservableObject {
|
|||
var dataSource: DataSource {
|
||||
Current.defaults.string(forKey: "dataSource").flatMap(DataSource.init(rawValue:)) ?? .default
|
||||
}
|
||||
|
||||
var savedUsername: String? {
|
||||
Current.defaults.string(forKey: "username")
|
||||
}
|
||||
|
||||
var hasSavedUsername: Bool {
|
||||
savedUsername != nil
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
|
|
@ -97,7 +105,7 @@ class AppState: ObservableObject {
|
|||
.handleEvents(receiveCompletion: { completion in
|
||||
if case .failure = completion {
|
||||
self.authenticationState = .unauthenticated
|
||||
self.presentingSignInAlert = true
|
||||
self.presentedSheet = .signIn
|
||||
}
|
||||
})
|
||||
.eraseToAnyPublisher()
|
||||
|
|
@ -107,7 +115,7 @@ class AppState: ObservableObject {
|
|||
validateSession()
|
||||
.catch { (error) -> AnyPublisher<Void, Error> in
|
||||
guard
|
||||
let username = Current.defaults.string(forKey: "username"),
|
||||
let username = self.savedUsername,
|
||||
let password = try? Current.keychain.getString(username)
|
||||
else {
|
||||
return Fail(error: error)
|
||||
|
|
@ -122,6 +130,7 @@ class AppState: ObservableObject {
|
|||
}
|
||||
|
||||
func signIn(username: String, password: String) {
|
||||
authError = nil
|
||||
signIn(username: username, password: password)
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
|
|
@ -150,12 +159,12 @@ class AppState: ObservableObject {
|
|||
}
|
||||
|
||||
func handleTwoFactorOption(_ option: TwoFactorOption, authOptions: AuthOptionsResponse, serviceKey: String, sessionID: String, scnt: String) {
|
||||
self.presentingSignInAlert = false
|
||||
self.secondFactorData = SecondFactorData(
|
||||
option: option,
|
||||
authOptions: authOptions,
|
||||
sessionData: AppleSessionData(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt)
|
||||
)
|
||||
self.presentedSheet = .twoFactor
|
||||
}
|
||||
|
||||
func requestSMS(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) {
|
||||
|
|
@ -201,7 +210,7 @@ class AppState: ObservableObject {
|
|||
switch completion {
|
||||
case let .failure(error):
|
||||
if case .invalidUsernameOrPassword = error as? AuthenticationError,
|
||||
let username = Current.defaults.string(forKey: "username") {
|
||||
let username = savedUsername {
|
||||
// remove any keychain password if we fail to log with an invalid username or password so it doesn't try again.
|
||||
try? Current.keychain.remove(username)
|
||||
Current.defaults.removeObject(forKey: "username")
|
||||
|
|
@ -212,7 +221,7 @@ class AppState: ObservableObject {
|
|||
case .finished:
|
||||
switch self.authenticationState {
|
||||
case .authenticated, .unauthenticated:
|
||||
self.presentingSignInAlert = false
|
||||
self.presentedSheet = nil
|
||||
self.secondFactorData = nil
|
||||
case let .waitingForSecondFactor(option, authOptions, sessionData):
|
||||
self.handleTwoFactorOption(option, authOptions: authOptions, serviceKey: sessionData.serviceKey, sessionID: sessionData.sessionID, scnt: sessionData.scnt)
|
||||
|
|
@ -221,11 +230,10 @@ class AppState: ObservableObject {
|
|||
}
|
||||
|
||||
func signOut() {
|
||||
let username = Current.defaults.string(forKey: "username")
|
||||
Current.defaults.removeObject(forKey: "username")
|
||||
if let username = username {
|
||||
if let username = savedUsername {
|
||||
try? Current.keychain.remove(username)
|
||||
}
|
||||
Current.defaults.removeObject(forKey: "username")
|
||||
AppleAPI.Current.network.session.configuration.httpCookieStorage?.removeCookies(since: .distantPast)
|
||||
authenticationState = .unauthenticated
|
||||
}
|
||||
|
|
@ -331,7 +339,10 @@ class AppState: ObservableObject {
|
|||
receiveCompletion: { [unowned self] completion in
|
||||
self.installationPublishers[id] = nil
|
||||
if case let .failure(error) = completion {
|
||||
self.error = error
|
||||
// Prevent setting the app state error if it is an invalid session, we will present the sign in view instead
|
||||
if error as? AuthenticationError != .invalidSession {
|
||||
self.error = error
|
||||
}
|
||||
if let index = self.allXcodes.firstIndex(where: { $0.id == id }) {
|
||||
self.allXcodes[index].installState = .notInstalled
|
||||
}
|
||||
|
|
|
|||
8
Xcodes/Frontend/Common/XcodesSheet.swift
Normal file
8
Xcodes/Frontend/Common/XcodesSheet.swift
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import Foundation
|
||||
|
||||
enum XcodesSheet: Identifiable {
|
||||
case signIn
|
||||
case twoFactor
|
||||
|
||||
var id: Int { hashValue }
|
||||
}
|
||||
|
|
@ -38,9 +38,15 @@ struct MainWindow: View {
|
|||
.navigationSubtitle(subtitleText)
|
||||
.frame(minWidth: 600, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
|
||||
.emittingError($appState.error, recoveryHandler: { _ in })
|
||||
.sheet(isPresented: $appState.secondFactorData.isNotNil) {
|
||||
secondFactorView(appState.secondFactorData!)
|
||||
.environmentObject(appState)
|
||||
.sheet(item: $appState.presentedSheet) { sheet in
|
||||
switch sheet {
|
||||
case .signIn:
|
||||
signInView()
|
||||
.environmentObject(appState)
|
||||
case .twoFactor:
|
||||
secondFactorView(appState.secondFactorData!)
|
||||
.environmentObject(appState)
|
||||
}
|
||||
}
|
||||
// This overlay is only here to work around the one-alert-per-view limitation
|
||||
.overlay(
|
||||
|
|
@ -111,6 +117,25 @@ struct MainWindow: View {
|
|||
SignInPhoneListView(isPresented: $appState.secondFactorData.isNotNil, authOptions: secondFactorData.authOptions, sessionData: secondFactorData.sessionData)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func signInView() -> some View {
|
||||
if appState.authenticationState == .authenticated {
|
||||
VStack {
|
||||
SignedInView()
|
||||
.padding(32)
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Close") { appState.presentedSheet = nil }
|
||||
.keyboardShortcut(.cancelAction)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
} else {
|
||||
SignInCredentialsView()
|
||||
.frame(width: 400)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MainWindow_Previews: PreviewProvider {
|
||||
|
|
|
|||
|
|
@ -7,28 +7,13 @@ struct GeneralPreferencePane: View {
|
|||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
GroupBox(label: Text("Apple ID")) {
|
||||
// If we have saved a username then we will show it here,
|
||||
// even if we don't have a valid session right now,
|
||||
// because we should be able to get a valid session if needed with the password in the keychain
|
||||
// and a 2FA code from the user.
|
||||
// Note that AppState.authenticationState is not necessarily .authenticated in this case, though.
|
||||
if let username = Current.defaults.string(forKey: "username") {
|
||||
HStack(alignment:.top, spacing: 10) {
|
||||
Text(username)
|
||||
Button("Sign Out", action: appState.signOut)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
Button("Sign In", action: { self.appState.presentingSignInAlert = true })
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
if appState.authenticationState == .authenticated {
|
||||
SignedInView()
|
||||
} else {
|
||||
Button("Sign In", action: { self.appState.presentedSheet = .signIn })
|
||||
}
|
||||
}
|
||||
.groupBoxStyle(PreferencesGroupBoxStyle())
|
||||
.sheet(isPresented: $appState.presentingSignInAlert) {
|
||||
SignInCredentialsView(isPresented: $appState.presentingSignInAlert)
|
||||
.environmentObject(appState)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
GroupBox(label: Text("Notifications")) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import SwiftUI
|
|||
|
||||
struct SignInCredentialsView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@Binding var isPresented: Bool
|
||||
@State private var username: String = ""
|
||||
@State private var password: String = ""
|
||||
|
||||
|
|
@ -15,36 +14,49 @@ struct SignInCredentialsView: View {
|
|||
Text("Apple ID:")
|
||||
.frame(minWidth: 100, alignment: .trailing)
|
||||
TextField("example@icloud.com", text: $username)
|
||||
.frame(width: 250)
|
||||
}
|
||||
HStack {
|
||||
Text("Password:")
|
||||
.frame(minWidth: 100, alignment: .trailing)
|
||||
SecureField("Required", text: $password)
|
||||
.frame(width: 250)
|
||||
}
|
||||
if appState.authError != nil {
|
||||
HStack {
|
||||
Text("")
|
||||
.frame(minWidth: 100)
|
||||
Text(appState.authError?.legibleLocalizedDescription ?? "")
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Cancel") { isPresented = false }
|
||||
.keyboardShortcut(.cancelAction)
|
||||
ProgressButton(isInProgress: appState.isProcessingAuthRequest,
|
||||
action: { appState.signIn(username: username, password: password) }) {
|
||||
Text("Next")
|
||||
Button("Cancel") {
|
||||
appState.authError = nil
|
||||
appState.presentedSheet = nil
|
||||
}
|
||||
.keyboardShortcut(.cancelAction)
|
||||
ProgressButton(
|
||||
isInProgress: appState.isProcessingAuthRequest,
|
||||
action: { appState.signIn(username: username, password: password) },
|
||||
label: {
|
||||
Text("Next")
|
||||
}
|
||||
)
|
||||
.disabled(username.isEmpty || password.isEmpty)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
.frame(height: 25)
|
||||
}
|
||||
.padding()
|
||||
.emittingError($appState.authError, recoveryHandler: { _ in })
|
||||
}
|
||||
}
|
||||
|
||||
struct SignInCredentialsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SignInCredentialsView(isPresented: .constant(true))
|
||||
SignInCredentialsView()
|
||||
.environmentObject(AppState())
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
Xcodes/Frontend/SignIn/SignedInView.swift
Normal file
24
Xcodes/Frontend/SignIn/SignedInView.swift
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import SwiftUI
|
||||
|
||||
struct SignedInView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
|
||||
private var username: String {
|
||||
appState.savedUsername ?? ""
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment:.top, spacing: 10) {
|
||||
Text(username)
|
||||
Button("Sign Out", action: appState.signOut)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
struct SignedInView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SignedInView()
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,11 @@ struct MainToolbarModifier: ViewModifier {
|
|||
|
||||
private var toolbar: some ToolbarContent {
|
||||
ToolbarItemGroup(placement: .status) {
|
||||
Button(action: { appState.presentedSheet = .signIn }, label: {
|
||||
Label("Login", systemImage: "person.circle")
|
||||
})
|
||||
.help("Login")
|
||||
|
||||
ProgressButton(
|
||||
isInProgress: appState.isUpdating,
|
||||
action: appState.update
|
||||
|
|
|
|||
|
|
@ -363,6 +363,73 @@ SOFTWARE.\
|
|||
\
|
||||
\
|
||||
|
||||
\fs34 Sparkle\
|
||||
\
|
||||
|
||||
\fs26 Copyright (c) 2006-2013 Andy Matuschak.\
|
||||
Copyright (c) 2009-2013 Elgato Systems GmbH.\
|
||||
Copyright (c) 2011-2014 Kornel Lesi\uc0\u324 ski.\
|
||||
Copyright (c) 2015-2017 Mayur Pawashe.\
|
||||
Copyright (c) 2014 C.W. Betts.\
|
||||
Copyright (c) 2014 Petroules Corporation.\
|
||||
Copyright (c) 2014 Big Nerd Ranch.\
|
||||
All rights reserved.\
|
||||
\
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of\
|
||||
this software and associated documentation files (the "Software"), to deal in\
|
||||
the Software without restriction, including without limitation the rights to\
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,\
|
||||
subject to the following conditions:\
|
||||
\
|
||||
The above copyright notice and this permission notice shall be included in all\
|
||||
copies or substantial portions of the Software.\
|
||||
\
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\
|
||||
\
|
||||
=================\
|
||||
EXTERNAL LICENSES\
|
||||
=================\
|
||||
\
|
||||
bspatch.c and bsdiff.c, from bsdiff 4.3 <http://www.daemonology.net/bsdiff/>:\
|
||||
Copyright (c) 2003-2005 Colin Percival.\
|
||||
\
|
||||
sais.c and sais.c, from sais-lite (2010/08/07) <https://sites.google.com/site/yuta256/sais>:\
|
||||
Copyright (c) 2008-2010 Yuta Mori.\
|
||||
\
|
||||
SUDSAVerifier.m:\
|
||||
Copyright (c) 2011 Mark Hamlin.\
|
||||
\
|
||||
All rights reserved.\
|
||||
\
|
||||
Redistribution and use in source and binary forms, with or without\
|
||||
modification, are permitted providing that the following conditions\
|
||||
are met:\
|
||||
1. Redistributions of source code must retain the above copyright\
|
||||
notice, this list of conditions and the following disclaimer.\
|
||||
2. Redistributions in binary form must reproduce the above copyright\
|
||||
notice, this list of conditions and the following disclaimer in the\
|
||||
documentation and/or other materials provided with the distribution.\
|
||||
\
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\
|
||||
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\
|
||||
POSSIBILITY OF SUCH DAMAGE.\
|
||||
\
|
||||
\
|
||||
|
||||
\fs34 LegibleError\
|
||||
\
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue