Present iPhone Git actions (#51)
This commit is contained in:
@@ -1099,11 +1099,15 @@ public protocol MobileHomeOperationProtocol: AnyObject, Sendable {
|
|||||||
|
|
||||||
func cancel()
|
func cancel()
|
||||||
|
|
||||||
|
func commit(message: String) throws -> MobileHomePage
|
||||||
|
|
||||||
|
func fetch() throws -> MobileHomePage
|
||||||
|
|
||||||
func progress() -> MobileHomeProgress
|
func progress() -> MobileHomeProgress
|
||||||
|
|
||||||
func pull() throws -> MobileHomePage
|
func pull() throws -> MobileHomePage
|
||||||
|
|
||||||
func refresh() throws -> MobileHomePage
|
func push() throws -> MobileHomePage
|
||||||
|
|
||||||
func refreshIfStale() throws -> MobileHomePage
|
func refreshIfStale() throws -> MobileHomePage
|
||||||
|
|
||||||
@@ -1178,6 +1182,25 @@ open func cancel() {try! rustCall() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func commit(message: String)throws -> MobileHomePage {
|
||||||
|
return try FfiConverterTypeMobileHomePage_lift(try rustCallWithError(FfiConverterTypeMobileHomeFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobilehomeoperation_commit(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterString.lower(message),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
open func fetch()throws -> MobileHomePage {
|
||||||
|
return try FfiConverterTypeMobileHomePage_lift(try rustCallWithError(FfiConverterTypeMobileHomeFfiError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_ironstorage_apple_fn_method_mobilehomeoperation_fetch(
|
||||||
|
self.uniffiCloneHandle(),uniffiCallStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
open func progress() -> MobileHomeProgress {
|
open func progress() -> MobileHomeProgress {
|
||||||
return try! FfiConverterTypeMobileHomeProgress_lift(try! rustCall() {
|
return try! FfiConverterTypeMobileHomeProgress_lift(try! rustCall() {
|
||||||
uniffiCallStatus in
|
uniffiCallStatus in
|
||||||
@@ -1196,10 +1219,10 @@ open func pull()throws -> MobileHomePage {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
open func refresh()throws -> MobileHomePage {
|
open func push()throws -> MobileHomePage {
|
||||||
return try FfiConverterTypeMobileHomePage_lift(try rustCallWithError(FfiConverterTypeMobileHomeFfiError_lift) {
|
return try FfiConverterTypeMobileHomePage_lift(try rustCallWithError(FfiConverterTypeMobileHomeFfiError_lift) {
|
||||||
uniffiCallStatus in
|
uniffiCallStatus in
|
||||||
uniffi_ironstorage_apple_fn_method_mobilehomeoperation_refresh(
|
uniffi_ironstorage_apple_fn_method_mobilehomeoperation_push(
|
||||||
self.uniffiCloneHandle(),uniffiCallStatus
|
self.uniffiCloneHandle(),uniffiCallStatus
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -2527,6 +2550,64 @@ public func FfiConverterTypeMobileGitIdentity_lower(_ value: MobileGitIdentity)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public struct MobileHomeAction: Equatable, Hashable {
|
||||||
|
public var kind: MobileHomeActionKind
|
||||||
|
public var title: String
|
||||||
|
public var systemImage: String
|
||||||
|
|
||||||
|
// Default memberwise initializers are never public by default, so we
|
||||||
|
// declare one manually.
|
||||||
|
public init(kind: MobileHomeActionKind, title: String, systemImage: String) {
|
||||||
|
self.kind = kind
|
||||||
|
self.title = title
|
||||||
|
self.systemImage = systemImage
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if compiler(>=6)
|
||||||
|
extension MobileHomeAction: Sendable {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public struct FfiConverterTypeMobileHomeAction: FfiConverterRustBuffer {
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileHomeAction {
|
||||||
|
return
|
||||||
|
try MobileHomeAction(
|
||||||
|
kind: FfiConverterTypeMobileHomeActionKind.read(from: &buf),
|
||||||
|
title: FfiConverterString.read(from: &buf),
|
||||||
|
systemImage: FfiConverterString.read(from: &buf)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func write(_ value: MobileHomeAction, into buf: inout [UInt8]) {
|
||||||
|
FfiConverterTypeMobileHomeActionKind.write(value.kind, into: &buf)
|
||||||
|
FfiConverterString.write(value.title, into: &buf)
|
||||||
|
FfiConverterString.write(value.systemImage, into: &buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileHomeAction_lift(_ buf: RustBuffer) throws -> MobileHomeAction {
|
||||||
|
return try FfiConverterTypeMobileHomeAction.lift(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileHomeAction_lower(_ value: MobileHomeAction) -> RustBuffer {
|
||||||
|
return FfiConverterTypeMobileHomeAction.lower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public struct MobileHomeChange: Equatable, Hashable {
|
public struct MobileHomeChange: Equatable, Hashable {
|
||||||
public var id: String
|
public var id: String
|
||||||
public var title: String
|
public var title: String
|
||||||
@@ -2604,16 +2685,18 @@ public struct MobileHomeCommit: Equatable, Hashable {
|
|||||||
public var systemImage: String
|
public var systemImage: String
|
||||||
public var timestamp: Int64
|
public var timestamp: Int64
|
||||||
public var changes: [MobileHomeChange]
|
public var changes: [MobileHomeChange]
|
||||||
|
public var actions: [MobileHomeAction]
|
||||||
|
|
||||||
// Default memberwise initializers are never public by default, so we
|
// Default memberwise initializers are never public by default, so we
|
||||||
// declare one manually.
|
// declare one manually.
|
||||||
public init(id: String, title: String, detail: String, systemImage: String, timestamp: Int64, changes: [MobileHomeChange]) {
|
public init(id: String, title: String, detail: String, systemImage: String, timestamp: Int64, changes: [MobileHomeChange], actions: [MobileHomeAction]) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.title = title
|
self.title = title
|
||||||
self.detail = detail
|
self.detail = detail
|
||||||
self.systemImage = systemImage
|
self.systemImage = systemImage
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
self.changes = changes
|
self.changes = changes
|
||||||
|
self.actions = actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2637,7 +2720,8 @@ public struct FfiConverterTypeMobileHomeCommit: FfiConverterRustBuffer {
|
|||||||
detail: FfiConverterString.read(from: &buf),
|
detail: FfiConverterString.read(from: &buf),
|
||||||
systemImage: FfiConverterString.read(from: &buf),
|
systemImage: FfiConverterString.read(from: &buf),
|
||||||
timestamp: FfiConverterInt64.read(from: &buf),
|
timestamp: FfiConverterInt64.read(from: &buf),
|
||||||
changes: FfiConverterSequenceTypeMobileHomeChange.read(from: &buf)
|
changes: FfiConverterSequenceTypeMobileHomeChange.read(from: &buf),
|
||||||
|
actions: FfiConverterSequenceTypeMobileHomeAction.read(from: &buf)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2648,6 +2732,7 @@ public struct FfiConverterTypeMobileHomeCommit: FfiConverterRustBuffer {
|
|||||||
FfiConverterString.write(value.systemImage, into: &buf)
|
FfiConverterString.write(value.systemImage, into: &buf)
|
||||||
FfiConverterInt64.write(value.timestamp, into: &buf)
|
FfiConverterInt64.write(value.timestamp, into: &buf)
|
||||||
FfiConverterSequenceTypeMobileHomeChange.write(value.changes, into: &buf)
|
FfiConverterSequenceTypeMobileHomeChange.write(value.changes, into: &buf)
|
||||||
|
FfiConverterSequenceTypeMobileHomeAction.write(value.actions, into: &buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2804,13 +2889,15 @@ public func FfiConverterTypeMobileHomePage_lower(_ value: MobileHomePage) -> Rus
|
|||||||
|
|
||||||
|
|
||||||
public struct MobileHomeProgress: Equatable, Hashable {
|
public struct MobileHomeProgress: Equatable, Hashable {
|
||||||
|
public var action: MobileHomeActionKind?
|
||||||
public var phase: MobileHomePhase
|
public var phase: MobileHomePhase
|
||||||
public var title: String
|
public var title: String
|
||||||
public var detail: String
|
public var detail: String
|
||||||
|
|
||||||
// Default memberwise initializers are never public by default, so we
|
// Default memberwise initializers are never public by default, so we
|
||||||
// declare one manually.
|
// declare one manually.
|
||||||
public init(phase: MobileHomePhase, title: String, detail: String) {
|
public init(action: MobileHomeActionKind?, phase: MobileHomePhase, title: String, detail: String) {
|
||||||
|
self.action = action
|
||||||
self.phase = phase
|
self.phase = phase
|
||||||
self.title = title
|
self.title = title
|
||||||
self.detail = detail
|
self.detail = detail
|
||||||
@@ -2832,6 +2919,7 @@ public struct FfiConverterTypeMobileHomeProgress: FfiConverterRustBuffer {
|
|||||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileHomeProgress {
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileHomeProgress {
|
||||||
return
|
return
|
||||||
try MobileHomeProgress(
|
try MobileHomeProgress(
|
||||||
|
action: FfiConverterOptionTypeMobileHomeActionKind.read(from: &buf),
|
||||||
phase: FfiConverterTypeMobileHomePhase.read(from: &buf),
|
phase: FfiConverterTypeMobileHomePhase.read(from: &buf),
|
||||||
title: FfiConverterString.read(from: &buf),
|
title: FfiConverterString.read(from: &buf),
|
||||||
detail: FfiConverterString.read(from: &buf)
|
detail: FfiConverterString.read(from: &buf)
|
||||||
@@ -2839,6 +2927,7 @@ public struct FfiConverterTypeMobileHomeProgress: FfiConverterRustBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func write(_ value: MobileHomeProgress, into buf: inout [UInt8]) {
|
public static func write(_ value: MobileHomeProgress, into buf: inout [UInt8]) {
|
||||||
|
FfiConverterOptionTypeMobileHomeActionKind.write(value.action, into: &buf)
|
||||||
FfiConverterTypeMobileHomePhase.write(value.phase, into: &buf)
|
FfiConverterTypeMobileHomePhase.write(value.phase, into: &buf)
|
||||||
FfiConverterString.write(value.title, into: &buf)
|
FfiConverterString.write(value.title, into: &buf)
|
||||||
FfiConverterString.write(value.detail, into: &buf)
|
FfiConverterString.write(value.detail, into: &buf)
|
||||||
@@ -2866,14 +2955,16 @@ public struct MobileHomeSummaryRow: Equatable, Hashable {
|
|||||||
public var title: String
|
public var title: String
|
||||||
public var detail: String
|
public var detail: String
|
||||||
public var systemImage: String
|
public var systemImage: String
|
||||||
|
public var actions: [MobileHomeAction]
|
||||||
|
|
||||||
// Default memberwise initializers are never public by default, so we
|
// Default memberwise initializers are never public by default, so we
|
||||||
// declare one manually.
|
// declare one manually.
|
||||||
public init(id: String, title: String, detail: String, systemImage: String) {
|
public init(id: String, title: String, detail: String, systemImage: String, actions: [MobileHomeAction]) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.title = title
|
self.title = title
|
||||||
self.detail = detail
|
self.detail = detail
|
||||||
self.systemImage = systemImage
|
self.systemImage = systemImage
|
||||||
|
self.actions = actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2895,7 +2986,8 @@ public struct FfiConverterTypeMobileHomeSummaryRow: FfiConverterRustBuffer {
|
|||||||
id: FfiConverterString.read(from: &buf),
|
id: FfiConverterString.read(from: &buf),
|
||||||
title: FfiConverterString.read(from: &buf),
|
title: FfiConverterString.read(from: &buf),
|
||||||
detail: FfiConverterString.read(from: &buf),
|
detail: FfiConverterString.read(from: &buf),
|
||||||
systemImage: FfiConverterString.read(from: &buf)
|
systemImage: FfiConverterString.read(from: &buf),
|
||||||
|
actions: FfiConverterSequenceTypeMobileHomeAction.read(from: &buf)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2904,6 +2996,7 @@ public struct FfiConverterTypeMobileHomeSummaryRow: FfiConverterRustBuffer {
|
|||||||
FfiConverterString.write(value.title, into: &buf)
|
FfiConverterString.write(value.title, into: &buf)
|
||||||
FfiConverterString.write(value.detail, into: &buf)
|
FfiConverterString.write(value.detail, into: &buf)
|
||||||
FfiConverterString.write(value.systemImage, into: &buf)
|
FfiConverterString.write(value.systemImage, into: &buf)
|
||||||
|
FfiConverterSequenceTypeMobileHomeAction.write(value.actions, into: &buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4619,6 +4712,86 @@ public func FfiConverterTypeMobileEntrySectionKind_lower(_ value: MobileEntrySec
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public enum MobileHomeActionKind: Equatable, Hashable {
|
||||||
|
|
||||||
|
case commit
|
||||||
|
case fetch
|
||||||
|
case pull
|
||||||
|
case push
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if compiler(>=6)
|
||||||
|
extension MobileHomeActionKind: Sendable {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public struct FfiConverterTypeMobileHomeActionKind: FfiConverterRustBuffer {
|
||||||
|
typealias SwiftType = MobileHomeActionKind
|
||||||
|
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileHomeActionKind {
|
||||||
|
let variant: Int32 = try readInt(&buf)
|
||||||
|
switch variant {
|
||||||
|
|
||||||
|
case 1: return .commit
|
||||||
|
|
||||||
|
case 2: return .fetch
|
||||||
|
|
||||||
|
case 3: return .pull
|
||||||
|
|
||||||
|
case 4: return .push
|
||||||
|
|
||||||
|
default: throw UniffiInternalError.unexpectedEnumCase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func write(_ value: MobileHomeActionKind, into buf: inout [UInt8]) {
|
||||||
|
switch value {
|
||||||
|
|
||||||
|
|
||||||
|
case .commit:
|
||||||
|
writeInt(&buf, Int32(1))
|
||||||
|
|
||||||
|
|
||||||
|
case .fetch:
|
||||||
|
writeInt(&buf, Int32(2))
|
||||||
|
|
||||||
|
|
||||||
|
case .pull:
|
||||||
|
writeInt(&buf, Int32(3))
|
||||||
|
|
||||||
|
|
||||||
|
case .push:
|
||||||
|
writeInt(&buf, Int32(4))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileHomeActionKind_lift(_ buf: RustBuffer) throws -> MobileHomeActionKind {
|
||||||
|
return try FfiConverterTypeMobileHomeActionKind.lift(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
public func FfiConverterTypeMobileHomeActionKind_lower(_ value: MobileHomeActionKind) -> RustBuffer {
|
||||||
|
return FfiConverterTypeMobileHomeActionKind.lower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public enum MobileHomeChangeKind: Equatable, Hashable {
|
public enum MobileHomeChangeKind: Equatable, Hashable {
|
||||||
|
|
||||||
case passwordEntry
|
case passwordEntry
|
||||||
@@ -4779,6 +4952,7 @@ public enum MobileHomeErrorKind: Equatable, Hashable {
|
|||||||
case authentication
|
case authentication
|
||||||
case conflict
|
case conflict
|
||||||
case dirtyLocalChanges
|
case dirtyLocalChanges
|
||||||
|
case noChanges
|
||||||
case offline
|
case offline
|
||||||
case interrupted
|
case interrupted
|
||||||
case secureStorage
|
case secureStorage
|
||||||
@@ -4815,15 +4989,17 @@ public struct FfiConverterTypeMobileHomeErrorKind: FfiConverterRustBuffer {
|
|||||||
|
|
||||||
case 5: return .dirtyLocalChanges
|
case 5: return .dirtyLocalChanges
|
||||||
|
|
||||||
case 6: return .offline
|
case 6: return .noChanges
|
||||||
|
|
||||||
case 7: return .interrupted
|
case 7: return .offline
|
||||||
|
|
||||||
case 8: return .secureStorage
|
case 8: return .interrupted
|
||||||
|
|
||||||
case 9: return .repository
|
case 9: return .secureStorage
|
||||||
|
|
||||||
case 10: return .partialProgress
|
case 10: return .repository
|
||||||
|
|
||||||
|
case 11: return .partialProgress
|
||||||
|
|
||||||
default: throw UniffiInternalError.unexpectedEnumCase
|
default: throw UniffiInternalError.unexpectedEnumCase
|
||||||
}
|
}
|
||||||
@@ -4853,25 +5029,29 @@ public struct FfiConverterTypeMobileHomeErrorKind: FfiConverterRustBuffer {
|
|||||||
writeInt(&buf, Int32(5))
|
writeInt(&buf, Int32(5))
|
||||||
|
|
||||||
|
|
||||||
case .offline:
|
case .noChanges:
|
||||||
writeInt(&buf, Int32(6))
|
writeInt(&buf, Int32(6))
|
||||||
|
|
||||||
|
|
||||||
case .interrupted:
|
case .offline:
|
||||||
writeInt(&buf, Int32(7))
|
writeInt(&buf, Int32(7))
|
||||||
|
|
||||||
|
|
||||||
case .secureStorage:
|
case .interrupted:
|
||||||
writeInt(&buf, Int32(8))
|
writeInt(&buf, Int32(8))
|
||||||
|
|
||||||
|
|
||||||
case .repository:
|
case .secureStorage:
|
||||||
writeInt(&buf, Int32(9))
|
writeInt(&buf, Int32(9))
|
||||||
|
|
||||||
|
|
||||||
case .partialProgress:
|
case .repository:
|
||||||
writeInt(&buf, Int32(10))
|
writeInt(&buf, Int32(10))
|
||||||
|
|
||||||
|
|
||||||
|
case .partialProgress:
|
||||||
|
writeInt(&buf, Int32(11))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5052,6 +5232,7 @@ public enum MobileHomePhase: Equatable, Hashable {
|
|||||||
case authenticating
|
case authenticating
|
||||||
case receiving
|
case receiving
|
||||||
case integrating
|
case integrating
|
||||||
|
case sending
|
||||||
case finishing
|
case finishing
|
||||||
|
|
||||||
|
|
||||||
@@ -5082,7 +5263,9 @@ public struct FfiConverterTypeMobileHomePhase: FfiConverterRustBuffer {
|
|||||||
|
|
||||||
case 4: return .integrating
|
case 4: return .integrating
|
||||||
|
|
||||||
case 5: return .finishing
|
case 5: return .sending
|
||||||
|
|
||||||
|
case 6: return .finishing
|
||||||
|
|
||||||
default: throw UniffiInternalError.unexpectedEnumCase
|
default: throw UniffiInternalError.unexpectedEnumCase
|
||||||
}
|
}
|
||||||
@@ -5108,9 +5291,13 @@ public struct FfiConverterTypeMobileHomePhase: FfiConverterRustBuffer {
|
|||||||
writeInt(&buf, Int32(4))
|
writeInt(&buf, Int32(4))
|
||||||
|
|
||||||
|
|
||||||
case .finishing:
|
case .sending:
|
||||||
writeInt(&buf, Int32(5))
|
writeInt(&buf, Int32(5))
|
||||||
|
|
||||||
|
|
||||||
|
case .finishing:
|
||||||
|
writeInt(&buf, Int32(6))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6428,6 +6615,30 @@ fileprivate struct FfiConverterOptionTypeMobileTotpPage: FfiConverterRustBuffer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
fileprivate struct FfiConverterOptionTypeMobileHomeActionKind: FfiConverterRustBuffer {
|
||||||
|
typealias SwiftType = MobileHomeActionKind?
|
||||||
|
|
||||||
|
public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
|
||||||
|
guard let value = value else {
|
||||||
|
writeInt(&buf, Int8(0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeInt(&buf, Int8(1))
|
||||||
|
FfiConverterTypeMobileHomeActionKind.write(value, into: &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
|
||||||
|
switch try readInt(&buf) as Int8 {
|
||||||
|
case 0: return nil
|
||||||
|
case 1: return try FfiConverterTypeMobileHomeActionKind.read(from: &buf)
|
||||||
|
default: throw UniffiInternalError.unexpectedOptionalTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if swift(>=5.8)
|
#if swift(>=5.8)
|
||||||
@_documentation(visibility: private)
|
@_documentation(visibility: private)
|
||||||
#endif
|
#endif
|
||||||
@@ -6553,6 +6764,31 @@ fileprivate struct FfiConverterSequenceTypeMobileEntrySection: FfiConverterRustB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.8)
|
||||||
|
@_documentation(visibility: private)
|
||||||
|
#endif
|
||||||
|
fileprivate struct FfiConverterSequenceTypeMobileHomeAction: FfiConverterRustBuffer {
|
||||||
|
typealias SwiftType = [MobileHomeAction]
|
||||||
|
|
||||||
|
public static func write(_ value: [MobileHomeAction], into buf: inout [UInt8]) {
|
||||||
|
let len = Int32(value.count)
|
||||||
|
writeInt(&buf, len)
|
||||||
|
for item in value {
|
||||||
|
FfiConverterTypeMobileHomeAction.write(item, into: &buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [MobileHomeAction] {
|
||||||
|
let len: Int32 = try readInt(&buf)
|
||||||
|
var seq = [MobileHomeAction]()
|
||||||
|
seq.reserveCapacity(Int(len))
|
||||||
|
for _ in 0 ..< len {
|
||||||
|
seq.append(try FfiConverterTypeMobileHomeAction.read(from: &buf))
|
||||||
|
}
|
||||||
|
return seq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if swift(>=5.8)
|
#if swift(>=5.8)
|
||||||
@_documentation(visibility: private)
|
@_documentation(visibility: private)
|
||||||
#endif
|
#endif
|
||||||
@@ -6784,10 +7020,11 @@ public func mobileAuthentication()throws -> MobileAuthentication {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
public func mobileHomeOperation() -> MobileHomeOperation {
|
public func mobileHomeOperation(authentication: MobileAuthentication) -> MobileHomeOperation {
|
||||||
return try! FfiConverterTypeMobileHomeOperation_lift(try! rustCall() {
|
return try! FfiConverterTypeMobileHomeOperation_lift(try! rustCall() {
|
||||||
uniffiCallStatus in
|
uniffiCallStatus in
|
||||||
uniffi_ironstorage_apple_fn_func_mobile_home_operation(uniffiCallStatus
|
uniffi_ironstorage_apple_fn_func_mobile_home_operation(
|
||||||
|
FfiConverterTypeMobileAuthentication_lower(authentication),uniffiCallStatus
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -6888,7 +7125,7 @@ private let initializationResult: InitializationResult = {
|
|||||||
if (uniffi_ironstorage_apple_checksum_func_mobile_authentication() != 38258) {
|
if (uniffi_ironstorage_apple_checksum_func_mobile_authentication() != 38258) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_func_mobile_home_operation() != 10595) {
|
if (uniffi_ironstorage_apple_checksum_func_mobile_home_operation() != 64540) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_func_mobile_key_transfer() != 57389) {
|
if (uniffi_ironstorage_apple_checksum_func_mobile_key_transfer() != 57389) {
|
||||||
@@ -7020,13 +7257,19 @@ private let initializationResult: InitializationResult = {
|
|||||||
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_cancel() != 13921) {
|
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_cancel() != 13921) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_commit() != 28659) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
|
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_fetch() != 29855) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_progress() != 4980) {
|
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_progress() != 4980) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_pull() != 9011) {
|
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_pull() != 9011) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_refresh() != 50081) {
|
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_push() != 33176) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_refresh_if_stale() != 27818) {
|
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_refresh_if_stale() != 27818) {
|
||||||
|
|||||||
@@ -428,6 +428,16 @@ RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_cached(uint64_
|
|||||||
void uniffi_ironstorage_apple_fn_method_mobilehomeoperation_cancel(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
void uniffi_ironstorage_apple_fn_method_mobilehomeoperation_cancel(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_COMMIT
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_COMMIT
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_commit(uint64_t ptr, RustBuffer message, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_FETCH
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_FETCH
|
||||||
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_fetch(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_PROGRESS
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_PROGRESS
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_PROGRESS
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_PROGRESS
|
||||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_progress(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_progress(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||||
@@ -438,9 +448,9 @@ RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_progress(uint6
|
|||||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_pull(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_pull(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_REFRESH
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_PUSH
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_REFRESH
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_PUSH
|
||||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_refresh(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_push(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_REFRESH_IF_STALE
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_REFRESH_IF_STALE
|
||||||
@@ -551,8 +561,7 @@ uint64_t uniffi_ironstorage_apple_fn_func_mobile_authentication(RustCallStatus *
|
|||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_HOME_OPERATION
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_HOME_OPERATION
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_HOME_OPERATION
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_HOME_OPERATION
|
||||||
uint64_t uniffi_ironstorage_apple_fn_func_mobile_home_operation(RustCallStatus *_Nonnull out_status
|
uint64_t uniffi_ironstorage_apple_fn_func_mobile_home_operation(uint64_t authentication, RustCallStatus *_Nonnull out_status
|
||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_KEY_TRANSFER
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_KEY_TRANSFER
|
||||||
@@ -1137,6 +1146,18 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_cached(voi
|
|||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_CANCEL
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_CANCEL
|
||||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_cancel(void
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_cancel(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_COMMIT
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_COMMIT
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_commit(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_FETCH
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_FETCH
|
||||||
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_fetch(void
|
||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_PROGRESS
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_PROGRESS
|
||||||
@@ -1151,9 +1172,9 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_pull(void
|
|||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_REFRESH
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_PUSH
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_REFRESH
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_PUSH
|
||||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_refresh(void
|
uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_push(void
|
||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ private final class AppContext: NSObject, UITabBarControllerDelegate {
|
|||||||
TotpListViewController(shellPage: page, authentication: authentication)
|
TotpListViewController(shellPage: page, authentication: authentication)
|
||||||
case .preferences:
|
case .preferences:
|
||||||
PreferencesViewController(page: page, authentication: authentication)
|
PreferencesViewController(page: page, authentication: authentication)
|
||||||
default:
|
case .home:
|
||||||
ShellViewController(page: page)
|
ShellViewController(page: page, authentication: authentication)
|
||||||
}
|
}
|
||||||
let navigation = UINavigationController(rootViewController: root)
|
let navigation = UINavigationController(rootViewController: root)
|
||||||
navigation.navigationBar.prefersLargeTitles = true
|
navigation.navigationBar.prefersLargeTitles = true
|
||||||
@@ -182,6 +182,7 @@ private final class AppContext: NSObject, UITabBarControllerDelegate {
|
|||||||
@MainActor
|
@MainActor
|
||||||
private final class ShellViewController: UITableViewController, MobileTabRoot {
|
private final class ShellViewController: UITableViewController, MobileTabRoot {
|
||||||
fileprivate let shellTab: MobileTab
|
fileprivate let shellTab: MobileTab
|
||||||
|
private let authentication: MobileAuthentication?
|
||||||
private var page: MobilePage
|
private var page: MobilePage
|
||||||
private var loadTask: Task<Void, Never>?
|
private var loadTask: Task<Void, Never>?
|
||||||
private var loadGeneration = 0
|
private var loadGeneration = 0
|
||||||
@@ -202,12 +203,20 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
|
|
||||||
private enum HomeRequest: Equatable {
|
private enum HomeRequest: Equatable {
|
||||||
case refreshIfStale
|
case refreshIfStale
|
||||||
case refresh
|
case commit(String)
|
||||||
|
case fetch
|
||||||
case pull
|
case pull
|
||||||
|
case push
|
||||||
|
|
||||||
|
var changesLocalStore: Bool {
|
||||||
|
if case .pull = self { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(page: MobilePage) {
|
init(page: MobilePage, authentication: MobileAuthentication?) {
|
||||||
shellTab = page.tab
|
shellTab = page.tab
|
||||||
|
self.authentication = authentication
|
||||||
self.page = page
|
self.page = page
|
||||||
super.init(style: .insetGrouped)
|
super.init(style: .insetGrouped)
|
||||||
title = page.title
|
title = page.title
|
||||||
@@ -344,6 +353,43 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func tableView(
|
||||||
|
_ tableView: UITableView,
|
||||||
|
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
|
||||||
|
) -> UISwipeActionsConfiguration? {
|
||||||
|
let actions = homeActions(at: indexPath).map { action in
|
||||||
|
let contextual = UIContextualAction(style: .normal, title: action.title) {
|
||||||
|
[weak self] _, _, completion in
|
||||||
|
self?.perform(action)
|
||||||
|
completion(true)
|
||||||
|
}
|
||||||
|
contextual.image = UIImage(systemName: action.systemImage)
|
||||||
|
contextual.backgroundColor = actionColor(action.kind)
|
||||||
|
return contextual
|
||||||
|
}
|
||||||
|
guard !actions.isEmpty else { return nil }
|
||||||
|
let configuration = UISwipeActionsConfiguration(actions: actions)
|
||||||
|
configuration.performsFirstActionWithFullSwipe = actions.count == 1
|
||||||
|
return configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(
|
||||||
|
_ tableView: UITableView,
|
||||||
|
contextMenuConfigurationForRowAt indexPath: IndexPath,
|
||||||
|
point: CGPoint
|
||||||
|
) -> UIContextMenuConfiguration? {
|
||||||
|
let actions = homeActions(at: indexPath)
|
||||||
|
guard !actions.isEmpty else { return nil }
|
||||||
|
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in
|
||||||
|
UIMenu(children: actions.map { action in
|
||||||
|
UIAction(
|
||||||
|
title: action.title,
|
||||||
|
image: UIImage(systemName: action.systemImage)
|
||||||
|
) { [weak self] _ in self?.perform(action) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func refreshRequested() {
|
@objc private func refreshRequested() {
|
||||||
if shellTab == .home, page.state == .ready {
|
if shellTab == .home, page.state == .ready {
|
||||||
guard !isHomeWorking else {
|
guard !isHomeWorking else {
|
||||||
@@ -434,7 +480,13 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func statusRefreshRequested() {
|
@objc private func statusRefreshRequested() {
|
||||||
runHome(.refresh)
|
runHome(.fetch)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func cancelHomeRequested() {
|
||||||
|
homeOperation?.cancel()
|
||||||
|
navigationItem.leftBarButtonItem?.isEnabled = false
|
||||||
|
navigationItem.prompt = "Cancelling the current Git action…"
|
||||||
}
|
}
|
||||||
|
|
||||||
private var homeSections: [HomeSection] {
|
private var homeSections: [HomeSection] {
|
||||||
@@ -466,18 +518,25 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
content.text = row.title
|
content.text = row.title
|
||||||
content.secondaryText = row.detail
|
content.secondaryText = row.detail
|
||||||
cell.selectionStyle = .none
|
cell.selectionStyle = .none
|
||||||
|
cell.accessibilityCustomActions = accessibilityActions(row.actions)
|
||||||
case .incoming:
|
case .incoming:
|
||||||
configureCommit(
|
configureCommit(
|
||||||
homePage.incoming[indexPath.row],
|
homePage.incoming[indexPath.row],
|
||||||
content: &content,
|
content: &content,
|
||||||
cell: cell
|
cell: cell
|
||||||
)
|
)
|
||||||
|
cell.accessibilityCustomActions = accessibilityActions(
|
||||||
|
homePage.incoming[indexPath.row].actions
|
||||||
|
)
|
||||||
case .outgoing:
|
case .outgoing:
|
||||||
configureCommit(
|
configureCommit(
|
||||||
homePage.outgoing[indexPath.row],
|
homePage.outgoing[indexPath.row],
|
||||||
content: &content,
|
content: &content,
|
||||||
cell: cell
|
cell: cell
|
||||||
)
|
)
|
||||||
|
cell.accessibilityCustomActions = accessibilityActions(
|
||||||
|
homePage.outgoing[indexPath.row].actions
|
||||||
|
)
|
||||||
case .empty:
|
case .empty:
|
||||||
content.image = UIImage(systemName: "checkmark.circle")
|
content.image = UIImage(systemName: "checkmark.circle")
|
||||||
content.text = "No Remote Activity"
|
content.text = "No Remote Activity"
|
||||||
@@ -533,7 +592,11 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
guard homePage == nil, homeTask == nil, !isHomeWorking else { return }
|
guard homePage == nil, homeTask == nil, !isHomeWorking else { return }
|
||||||
homeGeneration += 1
|
homeGeneration += 1
|
||||||
let current = homeGeneration
|
let current = homeGeneration
|
||||||
let operation = mobileHomeOperation()
|
guard let authentication else {
|
||||||
|
showHomeFailure(.unavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let operation = mobileHomeOperation(authentication: authentication)
|
||||||
homeOperation = operation
|
homeOperation = operation
|
||||||
isHomeWorking = true
|
isHomeWorking = true
|
||||||
navigationItem.rightBarButtonItem?.isEnabled = false
|
navigationItem.rightBarButtonItem?.isEnabled = false
|
||||||
@@ -558,10 +621,14 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
|
|
||||||
private func runHome(_ request: HomeRequest) {
|
private func runHome(_ request: HomeRequest) {
|
||||||
guard page.state == .ready, shellTab == .home, !isHomeWorking else { return }
|
guard page.state == .ready, shellTab == .home, !isHomeWorking else { return }
|
||||||
|
guard let authentication else {
|
||||||
|
showHomeFailure(.unavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
cancelHomeWork()
|
cancelHomeWork()
|
||||||
homeGeneration += 1
|
homeGeneration += 1
|
||||||
let current = homeGeneration
|
let current = homeGeneration
|
||||||
let operation = mobileHomeOperation()
|
let operation = mobileHomeOperation(authentication: authentication)
|
||||||
homeOperation = operation
|
homeOperation = operation
|
||||||
isHomeWorking = true
|
isHomeWorking = true
|
||||||
navigationItem.rightBarButtonItem?.isEnabled = false
|
navigationItem.rightBarButtonItem?.isEnabled = false
|
||||||
@@ -582,8 +649,10 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
do {
|
do {
|
||||||
let page: MobileHomePage = switch request {
|
let page: MobileHomePage = switch request {
|
||||||
case .refreshIfStale: try operation.refreshIfStale()
|
case .refreshIfStale: try operation.refreshIfStale()
|
||||||
case .refresh: try operation.refresh()
|
case let .commit(message): try operation.commit(message: message)
|
||||||
|
case .fetch: try operation.fetch()
|
||||||
case .pull: try operation.pull()
|
case .pull: try operation.pull()
|
||||||
|
case .push: try operation.push()
|
||||||
}
|
}
|
||||||
return Result<MobileHomePage, HomeFailure>.success(page)
|
return Result<MobileHomePage, HomeFailure>.success(page)
|
||||||
} catch let error as MobileHomeFfiError {
|
} catch let error as MobileHomeFfiError {
|
||||||
@@ -609,24 +678,32 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
navigationItem.titleView = nil
|
navigationItem.titleView = nil
|
||||||
navigationItem.prompt = nil
|
navigationItem.prompt = nil
|
||||||
navigationItem.rightBarButtonItem?.isEnabled = true
|
navigationItem.rightBarButtonItem?.isEnabled = true
|
||||||
|
navigationItem.leftBarButtonItem = nil
|
||||||
refreshControl?.endRefreshing()
|
refreshControl?.endRefreshing()
|
||||||
switch result {
|
switch result {
|
||||||
case let .success(homePage):
|
case let .success(homePage):
|
||||||
self.homePage = homePage
|
self.homePage = homePage
|
||||||
contentUnavailableConfiguration = nil
|
contentUnavailableConfiguration = nil
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
if request == .pull {
|
if request?.changesLocalStore == true {
|
||||||
NotificationCenter.default.post(name: .ironStorageLocalStoreDidChange, object: nil)
|
NotificationCenter.default.post(name: .ironStorageLocalStoreDidChange, object: nil)
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .ironStorageWatchSnapshotDidChange,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
if let notice = homePage.notice {
|
if let notice = homePage.notice {
|
||||||
UIAccessibility.post(notification: .announcement, argument: notice.title)
|
UIAccessibility.post(notification: .announcement, argument: notice.title)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
case let .failure(failure):
|
case let .failure(failure):
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
if homePage == nil {
|
if homePage == nil {
|
||||||
showHomeFailure(failure)
|
showHomeFailure(failure)
|
||||||
} else if failure.kind != .interrupted {
|
presentHomeFailure(failure, retry: request ?? .fetch)
|
||||||
presentHomeFailure(failure)
|
} else if failure.kind == .interrupted {
|
||||||
|
UIAccessibility.post(notification: .announcement, argument: failure.title)
|
||||||
|
} else {
|
||||||
|
presentHomeFailure(failure, retry: request ?? .fetch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -646,7 +723,7 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
contentUnavailableConfiguration = configuration
|
contentUnavailableConfiguration = configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentHomeFailure(_ failure: HomeFailure) {
|
private func presentHomeFailure(_ failure: HomeFailure, retry: HomeRequest) {
|
||||||
let alert = UIAlertController(
|
let alert = UIAlertController(
|
||||||
title: failure.title,
|
title: failure.title,
|
||||||
message: failure.detail,
|
message: failure.detail,
|
||||||
@@ -661,6 +738,9 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
} ?? tabs.selectedIndex
|
} ?? tabs.selectedIndex
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
alert.addAction(UIAlertAction(title: "Retry", style: .default) { [weak self] _ in
|
||||||
|
self?.runHome(retry)
|
||||||
|
})
|
||||||
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
|
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
|
||||||
present(alert, animated: true)
|
present(alert, animated: true)
|
||||||
}
|
}
|
||||||
@@ -677,6 +757,12 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
stack.spacing = 8
|
stack.spacing = 8
|
||||||
navigationItem.titleView = stack
|
navigationItem.titleView = stack
|
||||||
navigationItem.prompt = progress.detail
|
navigationItem.prompt = progress.detail
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
||||||
|
title: "Cancel",
|
||||||
|
style: .plain,
|
||||||
|
target: self,
|
||||||
|
action: #selector(cancelHomeRequested)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func cancelHomeWork() {
|
private func cancelHomeWork() {
|
||||||
@@ -690,10 +776,70 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
|||||||
isHomeWorking = false
|
isHomeWorking = false
|
||||||
navigationItem.titleView = nil
|
navigationItem.titleView = nil
|
||||||
navigationItem.prompt = nil
|
navigationItem.prompt = nil
|
||||||
|
navigationItem.leftBarButtonItem = nil
|
||||||
refreshControl?.endRefreshing()
|
refreshControl?.endRefreshing()
|
||||||
navigationItem.rightBarButtonItem?.isEnabled = true
|
navigationItem.rightBarButtonItem?.isEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func homeActions(at indexPath: IndexPath) -> [MobileHomeAction] {
|
||||||
|
guard let homePage, homeSections.indices.contains(indexPath.section) else { return [] }
|
||||||
|
return switch homeSections[indexPath.section] {
|
||||||
|
case .summary where homePage.summaries.indices.contains(indexPath.row):
|
||||||
|
homePage.summaries[indexPath.row].actions
|
||||||
|
case .incoming where homePage.incoming.indices.contains(indexPath.row):
|
||||||
|
homePage.incoming[indexPath.row].actions
|
||||||
|
case .outgoing where homePage.outgoing.indices.contains(indexPath.row):
|
||||||
|
homePage.outgoing[indexPath.row].actions
|
||||||
|
default:
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func perform(_ action: MobileHomeAction) {
|
||||||
|
switch action.kind {
|
||||||
|
case .commit: promptForCommitMessage()
|
||||||
|
case .fetch: runHome(.fetch)
|
||||||
|
case .pull: runHome(.pull)
|
||||||
|
case .push: runHome(.push)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func promptForCommitMessage() {
|
||||||
|
let alert = UIAlertController(
|
||||||
|
title: "Commit Changes",
|
||||||
|
message: "Enter the message for all current password-store changes.",
|
||||||
|
preferredStyle: .alert
|
||||||
|
)
|
||||||
|
alert.addTextField { field in
|
||||||
|
field.placeholder = "Commit message"
|
||||||
|
field.autocapitalizationType = .sentences
|
||||||
|
field.returnKeyType = .done
|
||||||
|
}
|
||||||
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||||
|
alert.addAction(UIAlertAction(title: "Commit", style: .default) { [weak self, weak alert] _ in
|
||||||
|
self?.runHome(.commit(alert?.textFields?.first?.text ?? ""))
|
||||||
|
})
|
||||||
|
present(alert, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func accessibilityActions(_ actions: [MobileHomeAction]) -> [UIAccessibilityCustomAction] {
|
||||||
|
actions.map { action in
|
||||||
|
UIAccessibilityCustomAction(name: action.title) { [weak self] _ in
|
||||||
|
self?.perform(action)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func actionColor(_ kind: MobileHomeActionKind) -> UIColor {
|
||||||
|
switch kind {
|
||||||
|
case .commit: .systemOrange
|
||||||
|
case .fetch: .systemGray
|
||||||
|
case .pull: .systemBlue
|
||||||
|
case .push: .systemGreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func stateImage(_ state: MobileShellState) -> String {
|
private func stateImage(_ state: MobileShellState) -> String {
|
||||||
switch state {
|
switch state {
|
||||||
case .loading: "hourglass"
|
case .loading: "hourglass"
|
||||||
@@ -5275,6 +5421,12 @@ private struct HomeFailure: Error, Sendable {
|
|||||||
detail: "IronStorage could not load the storage-provided Home page."
|
detail: "IronStorage could not load the storage-provided Home page."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
static let unavailable = HomeFailure(
|
||||||
|
kind: .configuration,
|
||||||
|
title: "Home Is Unavailable",
|
||||||
|
detail: "Complete password-store setup before using Git actions."
|
||||||
|
)
|
||||||
|
|
||||||
private init(kind: MobileHomeErrorKind, title: String, detail: String) {
|
private init(kind: MobileHomeErrorKind, title: String, detail: String) {
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ use ironstorage::{
|
|||||||
MobileEntrySectionKind as StorageEntrySectionKind,
|
MobileEntrySectionKind as StorageEntrySectionKind,
|
||||||
},
|
},
|
||||||
mobile_home::{
|
mobile_home::{
|
||||||
self, MobileHomeChangeKind as StorageHomeChangeKind,
|
self, MobileHomeActionKind as StorageHomeActionKind,
|
||||||
|
MobileHomeChangeKind as StorageHomeChangeKind,
|
||||||
MobileHomeChangeStatus as StorageHomeChangeStatus, MobileHomeError as StorageHomeError,
|
MobileHomeChangeStatus as StorageHomeChangeStatus, MobileHomeError as StorageHomeError,
|
||||||
MobileHomeErrorKind as StorageHomeErrorKind, MobileHomeFreshness as StorageHomeFreshness,
|
MobileHomeErrorKind as StorageHomeErrorKind, MobileHomeFreshness as StorageHomeFreshness,
|
||||||
MobileHomePhase as StorageHomePhase,
|
MobileHomePhase as StorageHomePhase,
|
||||||
@@ -189,6 +190,32 @@ pub enum MobileHomeChangeStatus {
|
|||||||
Deleted,
|
Deleted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
||||||
|
pub enum MobileHomeActionKind {
|
||||||
|
Commit,
|
||||||
|
Fetch,
|
||||||
|
Pull,
|
||||||
|
Push,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StorageHomeActionKind> for MobileHomeActionKind {
|
||||||
|
fn from(kind: StorageHomeActionKind) -> Self {
|
||||||
|
match kind {
|
||||||
|
StorageHomeActionKind::Commit => Self::Commit,
|
||||||
|
StorageHomeActionKind::Fetch => Self::Fetch,
|
||||||
|
StorageHomeActionKind::Pull => Self::Pull,
|
||||||
|
StorageHomeActionKind::Push => Self::Push,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, uniffi::Record)]
|
||||||
|
pub struct MobileHomeAction {
|
||||||
|
pub kind: MobileHomeActionKind,
|
||||||
|
pub title: String,
|
||||||
|
pub system_image: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl From<StorageHomeChangeStatus> for MobileHomeChangeStatus {
|
impl From<StorageHomeChangeStatus> for MobileHomeChangeStatus {
|
||||||
fn from(status: StorageHomeChangeStatus) -> Self {
|
fn from(status: StorageHomeChangeStatus) -> Self {
|
||||||
match status {
|
match status {
|
||||||
@@ -205,6 +232,7 @@ pub struct MobileHomeSummaryRow {
|
|||||||
pub title: String,
|
pub title: String,
|
||||||
pub detail: String,
|
pub detail: String,
|
||||||
pub system_image: String,
|
pub system_image: String,
|
||||||
|
pub actions: Vec<MobileHomeAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, uniffi::Record)]
|
#[derive(Clone, uniffi::Record)]
|
||||||
@@ -225,6 +253,7 @@ pub struct MobileHomeCommit {
|
|||||||
pub system_image: String,
|
pub system_image: String,
|
||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
pub changes: Vec<MobileHomeChange>,
|
pub changes: Vec<MobileHomeChange>,
|
||||||
|
pub actions: Vec<MobileHomeAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, uniffi::Record)]
|
#[derive(Clone, uniffi::Record)]
|
||||||
@@ -259,6 +288,7 @@ impl From<mobile_home::MobileHomePage> for MobileHomePage {
|
|||||||
title: row.title().to_owned(),
|
title: row.title().to_owned(),
|
||||||
detail: row.detail().to_owned(),
|
detail: row.detail().to_owned(),
|
||||||
system_image: row.system_image().to_owned(),
|
system_image: row.system_image().to_owned(),
|
||||||
|
actions: row.actions().iter().map(mobile_home_action).collect(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
incoming: page.incoming().iter().map(mobile_home_commit).collect(),
|
incoming: page.incoming().iter().map(mobile_home_commit).collect(),
|
||||||
@@ -293,6 +323,15 @@ fn mobile_home_commit(commit: &mobile_home::MobileHomeCommit) -> MobileHomeCommi
|
|||||||
status: change.status().into(),
|
status: change.status().into(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
actions: commit.actions().iter().map(mobile_home_action).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mobile_home_action(action: &mobile_home::MobileHomeAction) -> MobileHomeAction {
|
||||||
|
MobileHomeAction {
|
||||||
|
kind: action.kind().into(),
|
||||||
|
title: action.title().to_owned(),
|
||||||
|
system_image: action.system_image().to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +341,7 @@ pub enum MobileHomePhase {
|
|||||||
Authenticating,
|
Authenticating,
|
||||||
Receiving,
|
Receiving,
|
||||||
Integrating,
|
Integrating,
|
||||||
|
Sending,
|
||||||
Finishing,
|
Finishing,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,6 +352,7 @@ impl From<StorageHomePhase> for MobileHomePhase {
|
|||||||
StorageHomePhase::Authenticating => Self::Authenticating,
|
StorageHomePhase::Authenticating => Self::Authenticating,
|
||||||
StorageHomePhase::Receiving => Self::Receiving,
|
StorageHomePhase::Receiving => Self::Receiving,
|
||||||
StorageHomePhase::Integrating => Self::Integrating,
|
StorageHomePhase::Integrating => Self::Integrating,
|
||||||
|
StorageHomePhase::Sending => Self::Sending,
|
||||||
StorageHomePhase::Finishing => Self::Finishing,
|
StorageHomePhase::Finishing => Self::Finishing,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,6 +360,7 @@ impl From<StorageHomePhase> for MobileHomePhase {
|
|||||||
|
|
||||||
#[derive(Clone, uniffi::Record)]
|
#[derive(Clone, uniffi::Record)]
|
||||||
pub struct MobileHomeProgress {
|
pub struct MobileHomeProgress {
|
||||||
|
pub action: Option<MobileHomeActionKind>,
|
||||||
pub phase: MobileHomePhase,
|
pub phase: MobileHomePhase,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub detail: String,
|
pub detail: String,
|
||||||
@@ -331,6 +373,7 @@ pub enum MobileHomeErrorKind {
|
|||||||
Authentication,
|
Authentication,
|
||||||
Conflict,
|
Conflict,
|
||||||
DirtyLocalChanges,
|
DirtyLocalChanges,
|
||||||
|
NoChanges,
|
||||||
Offline,
|
Offline,
|
||||||
Interrupted,
|
Interrupted,
|
||||||
SecureStorage,
|
SecureStorage,
|
||||||
@@ -346,6 +389,7 @@ impl From<StorageHomeErrorKind> for MobileHomeErrorKind {
|
|||||||
StorageHomeErrorKind::Authentication => Self::Authentication,
|
StorageHomeErrorKind::Authentication => Self::Authentication,
|
||||||
StorageHomeErrorKind::Conflict => Self::Conflict,
|
StorageHomeErrorKind::Conflict => Self::Conflict,
|
||||||
StorageHomeErrorKind::DirtyLocalChanges => Self::DirtyLocalChanges,
|
StorageHomeErrorKind::DirtyLocalChanges => Self::DirtyLocalChanges,
|
||||||
|
StorageHomeErrorKind::NoChanges => Self::NoChanges,
|
||||||
StorageHomeErrorKind::Offline => Self::Offline,
|
StorageHomeErrorKind::Offline => Self::Offline,
|
||||||
StorageHomeErrorKind::Interrupted => Self::Interrupted,
|
StorageHomeErrorKind::Interrupted => Self::Interrupted,
|
||||||
StorageHomeErrorKind::SecureStorage => Self::SecureStorage,
|
StorageHomeErrorKind::SecureStorage => Self::SecureStorage,
|
||||||
@@ -394,6 +438,7 @@ impl MobileHomeOperation {
|
|||||||
pub fn progress(&self) -> MobileHomeProgress {
|
pub fn progress(&self) -> MobileHomeProgress {
|
||||||
let progress = self.operation.progress();
|
let progress = self.operation.progress();
|
||||||
MobileHomeProgress {
|
MobileHomeProgress {
|
||||||
|
action: progress.action().map(Into::into),
|
||||||
phase: progress.phase().into(),
|
phase: progress.phase().into(),
|
||||||
title: progress.title().to_owned(),
|
title: progress.title().to_owned(),
|
||||||
detail: progress.detail().to_owned(),
|
detail: progress.detail().to_owned(),
|
||||||
@@ -411,14 +456,25 @@ impl MobileHomeOperation {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
pub fn commit(&self, message: String) -> Result<MobileHomePage, MobileHomeFfiError> {
|
||||||
self.operation.refresh().map(Into::into).map_err(Into::into)
|
self.operation
|
||||||
|
.commit(&message)
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
||||||
|
self.operation.fetch().map(Into::into).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pull(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
pub fn pull(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
||||||
self.operation.pull().map(Into::into).map_err(Into::into)
|
self.operation.pull().map(Into::into).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn push(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
||||||
|
self.operation.push().map(Into::into).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cancel(&self) {
|
pub fn cancel(&self) {
|
||||||
self.operation.cancel();
|
self.operation.cancel();
|
||||||
}
|
}
|
||||||
@@ -1322,7 +1378,7 @@ impl From<StorageAuthenticationError> for MobileAuthenticationFfiError {
|
|||||||
|
|
||||||
#[derive(uniffi::Object)]
|
#[derive(uniffi::Object)]
|
||||||
pub struct MobileAuthentication {
|
pub struct MobileAuthentication {
|
||||||
authentication: ironstorage::mobile_authentication::MobileAuthentication,
|
authentication: Arc<ironstorage::mobile_authentication::MobileAuthentication>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[uniffi::export]
|
#[uniffi::export]
|
||||||
@@ -1855,9 +1911,13 @@ pub fn set_selected_mobile_tab(tab: MobileTab) -> Result<(), MobilePreferenceErr
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[uniffi::export]
|
#[uniffi::export]
|
||||||
pub fn mobile_home_operation() -> Arc<MobileHomeOperation> {
|
pub fn mobile_home_operation(
|
||||||
|
authentication: Arc<MobileAuthentication>,
|
||||||
|
) -> Arc<MobileHomeOperation> {
|
||||||
Arc::new(MobileHomeOperation {
|
Arc::new(MobileHomeOperation {
|
||||||
operation: mobile_home::MobileHomeOperation::default(),
|
operation: mobile_home::MobileHomeOperation::new(Arc::clone(
|
||||||
|
&authentication.authentication,
|
||||||
|
)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1887,7 +1947,7 @@ pub fn mobile_password_search(query: String) -> Result<MobilePasswordPage, Mobil
|
|||||||
#[uniffi::export]
|
#[uniffi::export]
|
||||||
pub fn mobile_authentication() -> Result<Arc<MobileAuthentication>, MobileAuthenticationFfiError> {
|
pub fn mobile_authentication() -> Result<Arc<MobileAuthentication>, MobileAuthenticationFfiError> {
|
||||||
Ok(Arc::new(MobileAuthentication {
|
Ok(Arc::new(MobileAuthentication {
|
||||||
authentication: ironstorage::mobile_authentication::MobileAuthentication::load()?,
|
authentication: Arc::new(ironstorage::mobile_authentication::MobileAuthentication::load()?),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1931,8 +1991,8 @@ pub fn replace_configured_mobile_application_token(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
MobileHomePhase, MobileOnboardingErrorKind, MobileOnboardingFfiError, MobileShellState,
|
MobileHomeActionKind, MobileOnboardingErrorKind, MobileOnboardingFfiError,
|
||||||
MobileTab,
|
MobileShellState, MobileTab, StorageHomeActionKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1976,10 +2036,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn home_operation_exposes_typed_progress_and_cancellation() {
|
fn home_bridge_preserves_storage_action_kinds() {
|
||||||
let operation = super::mobile_home_operation();
|
assert_eq!(
|
||||||
assert_eq!(operation.progress().phase, MobileHomePhase::Validating);
|
MobileHomeActionKind::from(StorageHomeActionKind::Push),
|
||||||
operation.cancel();
|
MobileHomeActionKind::Push
|
||||||
assert_eq!(operation.progress().phase, MobileHomePhase::Validating);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2255,6 +2255,25 @@ impl GitRepository {
|
|||||||
self.commit_tree(message, tree)
|
self.commit_tree(message, tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stage every current worktree/index change and create one user-requested
|
||||||
|
/// commit without exposing staging policy to a frontend.
|
||||||
|
pub fn commit_all(&self, message: &str) -> Result<String, GitError> {
|
||||||
|
let status = self.status()?;
|
||||||
|
let paths = status
|
||||||
|
.staged()
|
||||||
|
.iter()
|
||||||
|
.chain(status.unstaged())
|
||||||
|
.map(|change| change.path().to_owned())
|
||||||
|
.collect::<BTreeSet<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if paths.is_empty() {
|
||||||
|
return Err(GitError::NoChanges);
|
||||||
|
}
|
||||||
|
self.stage_and_commit(&paths, message)?
|
||||||
|
.ok_or(GitError::NoChanges)
|
||||||
|
}
|
||||||
|
|
||||||
fn commit_tree(&self, message: &str, tree: gix::hash::ObjectId) -> Result<String, GitError> {
|
fn commit_tree(&self, message: &str, tree: gix::hash::ObjectId) -> Result<String, GitError> {
|
||||||
validate_commit_message(message)?;
|
validate_commit_message(message)?;
|
||||||
let parent = self.repository.head_id().ok().map(|id| id.detach());
|
let parent = self.repository.head_id().ok().map(|id| id.detach());
|
||||||
|
|||||||
@@ -195,9 +195,38 @@ struct MobileAuthenticationStatus {
|
|||||||
next_editor_id: u64,
|
next_editor_id: u64,
|
||||||
editors: BTreeMap<u64, MobileEntryDraft>,
|
editors: BTreeMap<u64, MobileEntryDraft>,
|
||||||
mutation_active: bool,
|
mutation_active: bool,
|
||||||
|
repository_operation_active: bool,
|
||||||
watch_shared_totp_entries: std::collections::BTreeSet<EntryPath>,
|
watch_shared_totp_entries: std::collections::BTreeSet<EntryPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum MobileRepositoryOperation {
|
||||||
|
Commit,
|
||||||
|
Fetch,
|
||||||
|
Pull,
|
||||||
|
Push,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum MobileRepositoryOperationError {
|
||||||
|
Busy,
|
||||||
|
DirtyEditor,
|
||||||
|
OpenEditor,
|
||||||
|
Unavailable,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct MobileRepositoryOperationGuard<'a> {
|
||||||
|
authentication: &'a MobileAuthentication,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for MobileRepositoryOperationGuard<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Ok(mut status) = self.authentication.status.lock() {
|
||||||
|
status.repository_operation_active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// One process-wide mobile authentication lease shared by every tab and viewer.
|
/// One process-wide mobile authentication lease shared by every tab and viewer.
|
||||||
pub struct MobileAuthentication {
|
pub struct MobileAuthentication {
|
||||||
config: Config,
|
config: Config,
|
||||||
@@ -232,6 +261,7 @@ impl MobileAuthentication {
|
|||||||
next_editor_id: 0,
|
next_editor_id: 0,
|
||||||
editors: BTreeMap::new(),
|
editors: BTreeMap::new(),
|
||||||
mutation_active: false,
|
mutation_active: false,
|
||||||
|
repository_operation_active: false,
|
||||||
watch_shared_totp_entries: config.watch_shared_totp_entries().clone(),
|
watch_shared_totp_entries: config.watch_shared_totp_entries().clone(),
|
||||||
}),
|
}),
|
||||||
config,
|
config,
|
||||||
@@ -667,6 +697,7 @@ impl MobileAuthentication {
|
|||||||
field: u64,
|
field: u64,
|
||||||
value: String,
|
value: String,
|
||||||
) -> Result<MobileEntryPage, MobileAuthenticationError> {
|
) -> Result<MobileEntryPage, MobileAuthenticationError> {
|
||||||
|
self.ensure_repository_idle()?;
|
||||||
let mut document = self.open_active_document(path)?;
|
let mut document = self.open_active_document(path)?;
|
||||||
document
|
document
|
||||||
.replace_field_value(EntryFieldId::from_value(field), value.into_bytes())
|
.replace_field_value(EntryFieldId::from_value(field), value.into_bytes())
|
||||||
@@ -824,6 +855,7 @@ impl MobileAuthentication {
|
|||||||
fields: Vec<MobileEntryEditorInput>,
|
fields: Vec<MobileEntryEditorInput>,
|
||||||
) -> Result<MobileEntryPage, MobileAuthenticationError> {
|
) -> Result<MobileEntryPage, MobileAuthenticationError> {
|
||||||
self.ensure_active()?;
|
self.ensure_active()?;
|
||||||
|
self.ensure_repository_idle()?;
|
||||||
let mut draft = self
|
let mut draft = self
|
||||||
.status()?
|
.status()?
|
||||||
.editors
|
.editors
|
||||||
@@ -939,10 +971,10 @@ impl MobileAuthentication {
|
|||||||
draft: MobileEntryDraft,
|
draft: MobileEntryDraft,
|
||||||
) -> Result<MobileEntryEditorSession, MobileAuthenticationError> {
|
) -> Result<MobileEntryEditorSession, MobileAuthenticationError> {
|
||||||
let mut status = self.status()?;
|
let mut status = self.status()?;
|
||||||
if status.mutation_active {
|
if status.mutation_active || status.repository_operation_active {
|
||||||
return Err(entry_detail(
|
return Err(entry_detail(
|
||||||
"Password Action In Progress",
|
"Password Action In Progress",
|
||||||
"wait for the current move, copy, or delete action to finish",
|
"wait for the current password-store action to finish",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let id = status.next_editor_id;
|
let id = status.next_editor_id;
|
||||||
@@ -960,7 +992,7 @@ impl MobileAuthentication {
|
|||||||
fn reserve_entry_mutation(&self) -> Result<(), MobileAuthenticationError> {
|
fn reserve_entry_mutation(&self) -> Result<(), MobileAuthenticationError> {
|
||||||
let mut status = self.status()?;
|
let mut status = self.status()?;
|
||||||
// ponytail: serialize mobile mutations; use per-path reservations if concurrent UI needs it.
|
// ponytail: serialize mobile mutations; use per-path reservations if concurrent UI needs it.
|
||||||
if status.mutation_active {
|
if status.mutation_active || status.repository_operation_active {
|
||||||
return Err(MobileAuthenticationError::new(
|
return Err(MobileAuthenticationError::new(
|
||||||
MobileAuthenticationErrorKind::Conflict,
|
MobileAuthenticationErrorKind::Conflict,
|
||||||
"Password Action In Progress",
|
"Password Action In Progress",
|
||||||
@@ -971,6 +1003,44 @@ impl MobileAuthentication {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn reserve_repository_operation(
|
||||||
|
&self,
|
||||||
|
operation: MobileRepositoryOperation,
|
||||||
|
) -> Result<MobileRepositoryOperationGuard<'_>, MobileRepositoryOperationError> {
|
||||||
|
let mut status = self
|
||||||
|
.status
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| MobileRepositoryOperationError::Unavailable)?;
|
||||||
|
let dirty_editor = status
|
||||||
|
.editors
|
||||||
|
.values()
|
||||||
|
.any(|draft| draft.document().is_modified());
|
||||||
|
if let Some(error) = repository_operation_conflict(
|
||||||
|
operation,
|
||||||
|
status.mutation_active || status.repository_operation_active,
|
||||||
|
!status.editors.is_empty(),
|
||||||
|
dirty_editor,
|
||||||
|
) {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
status.repository_operation_active = true;
|
||||||
|
drop(status);
|
||||||
|
Ok(MobileRepositoryOperationGuard {
|
||||||
|
authentication: self,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_repository_idle(&self) -> Result<(), MobileAuthenticationError> {
|
||||||
|
if self.status()?.repository_operation_active {
|
||||||
|
Err(entry_detail(
|
||||||
|
"Repository Action In Progress",
|
||||||
|
"wait for the current commit, fetch, pull, or push action to finish",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn release_entry_mutation(&self) {
|
fn release_entry_mutation(&self) {
|
||||||
if let Ok(mut status) = self.status.lock() {
|
if let Ok(mut status) = self.status.lock() {
|
||||||
status.mutation_active = false;
|
status.mutation_active = false;
|
||||||
@@ -1061,6 +1131,27 @@ impl MobileAuthentication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn repository_operation_conflict(
|
||||||
|
operation: MobileRepositoryOperation,
|
||||||
|
busy: bool,
|
||||||
|
open_editor: bool,
|
||||||
|
dirty_editor: bool,
|
||||||
|
) -> Option<MobileRepositoryOperationError> {
|
||||||
|
if busy {
|
||||||
|
Some(MobileRepositoryOperationError::Busy)
|
||||||
|
} else if operation == MobileRepositoryOperation::Pull && open_editor {
|
||||||
|
Some(if dirty_editor {
|
||||||
|
MobileRepositoryOperationError::DirtyEditor
|
||||||
|
} else {
|
||||||
|
MobileRepositoryOperationError::OpenEditor
|
||||||
|
})
|
||||||
|
} else if operation == MobileRepositoryOperation::Push && dirty_editor {
|
||||||
|
Some(MobileRepositoryOperationError::DirtyEditor)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct KeyOnlyProvider<'a> {
|
struct KeyOnlyProvider<'a> {
|
||||||
handle: NativeAuthenticationHandle,
|
handle: NativeAuthenticationHandle,
|
||||||
fingerprint: &'a str,
|
fingerprint: &'a str,
|
||||||
@@ -1198,3 +1289,34 @@ fn entry_detail(title: &str, error: impl fmt::Display) -> MobileAuthenticationEr
|
|||||||
error.to_string(),
|
error.to_string(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{
|
||||||
|
MobileRepositoryOperation, MobileRepositoryOperationError, repository_operation_conflict,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repository_actions_protect_editors_and_serialize_mutations() {
|
||||||
|
assert_eq!(
|
||||||
|
repository_operation_conflict(MobileRepositoryOperation::Pull, false, true, false),
|
||||||
|
Some(MobileRepositoryOperationError::OpenEditor)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
repository_operation_conflict(MobileRepositoryOperation::Pull, false, true, true),
|
||||||
|
Some(MobileRepositoryOperationError::DirtyEditor)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
repository_operation_conflict(MobileRepositoryOperation::Push, false, true, true),
|
||||||
|
Some(MobileRepositoryOperationError::DirtyEditor)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
repository_operation_conflict(MobileRepositoryOperation::Fetch, false, true, true),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
repository_operation_conflict(MobileRepositoryOperation::Commit, true, false, false),
|
||||||
|
Some(MobileRepositoryOperationError::Busy)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, ConfigError, GitRemote},
|
config::{Config, ConfigError, GitRemote},
|
||||||
git::{
|
git::{
|
||||||
GitChangeKind, GitCommitActivity, GitDivergence, GitError, GitOperationControl,
|
FetchOutcome, GitChangeKind, GitCommitActivity, GitDivergence, GitError,
|
||||||
GitProgressPhase, GitRepository, PullOutcome,
|
GitOperationControl, GitProgressPhase, GitRepository, PullOutcome, PushOutcome,
|
||||||
|
},
|
||||||
|
mobile_authentication::{
|
||||||
|
MobileAuthentication, MobileRepositoryOperation, MobileRepositoryOperationError,
|
||||||
},
|
},
|
||||||
repository::{Repository, RepositoryError},
|
repository::{Repository, RepositoryError},
|
||||||
secret_store::{
|
secret_store::{
|
||||||
@@ -46,6 +49,35 @@ pub enum MobileHomeChangeStatus {
|
|||||||
Deleted,
|
Deleted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum MobileHomeActionKind {
|
||||||
|
Commit,
|
||||||
|
Fetch,
|
||||||
|
Pull,
|
||||||
|
Push,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MobileHomeAction {
|
||||||
|
kind: MobileHomeActionKind,
|
||||||
|
title: &'static str,
|
||||||
|
system_image: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MobileHomeAction {
|
||||||
|
pub fn kind(&self) -> MobileHomeActionKind {
|
||||||
|
self.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(&self) -> &str {
|
||||||
|
self.title
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn system_image(&self) -> &str {
|
||||||
|
self.system_image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<GitChangeKind> for MobileHomeChangeStatus {
|
impl From<GitChangeKind> for MobileHomeChangeStatus {
|
||||||
fn from(kind: GitChangeKind) -> Self {
|
fn from(kind: GitChangeKind) -> Self {
|
||||||
match kind {
|
match kind {
|
||||||
@@ -62,6 +94,7 @@ pub struct MobileHomeSummaryRow {
|
|||||||
title: String,
|
title: String,
|
||||||
detail: String,
|
detail: String,
|
||||||
system_image: String,
|
system_image: String,
|
||||||
|
actions: Vec<MobileHomeAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MobileHomeSummaryRow {
|
impl MobileHomeSummaryRow {
|
||||||
@@ -80,6 +113,10 @@ impl MobileHomeSummaryRow {
|
|||||||
pub fn system_image(&self) -> &str {
|
pub fn system_image(&self) -> &str {
|
||||||
&self.system_image
|
&self.system_image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn actions(&self) -> &[MobileHomeAction] {
|
||||||
|
&self.actions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
@@ -126,6 +163,7 @@ pub struct MobileHomeCommit {
|
|||||||
system_image: String,
|
system_image: String,
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
changes: Vec<MobileHomeChange>,
|
changes: Vec<MobileHomeChange>,
|
||||||
|
actions: Vec<MobileHomeAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MobileHomeCommit {
|
impl MobileHomeCommit {
|
||||||
@@ -152,6 +190,10 @@ impl MobileHomeCommit {
|
|||||||
pub fn changes(&self) -> &[MobileHomeChange] {
|
pub fn changes(&self) -> &[MobileHomeChange] {
|
||||||
&self.changes
|
&self.changes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn actions(&self) -> &[MobileHomeAction] {
|
||||||
|
&self.actions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
@@ -227,6 +269,7 @@ pub enum MobileHomePhase {
|
|||||||
Authenticating,
|
Authenticating,
|
||||||
Receiving,
|
Receiving,
|
||||||
Integrating,
|
Integrating,
|
||||||
|
Sending,
|
||||||
Finishing,
|
Finishing,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +279,8 @@ impl From<GitProgressPhase> for MobileHomePhase {
|
|||||||
GitProgressPhase::Validating => Self::Validating,
|
GitProgressPhase::Validating => Self::Validating,
|
||||||
GitProgressPhase::Authenticating => Self::Authenticating,
|
GitProgressPhase::Authenticating => Self::Authenticating,
|
||||||
GitProgressPhase::Receiving => Self::Receiving,
|
GitProgressPhase::Receiving => Self::Receiving,
|
||||||
GitProgressPhase::Integrating | GitProgressPhase::Sending => Self::Integrating,
|
GitProgressPhase::Integrating => Self::Integrating,
|
||||||
|
GitProgressPhase::Sending => Self::Sending,
|
||||||
GitProgressPhase::Refreshing => Self::Finishing,
|
GitProgressPhase::Refreshing => Self::Finishing,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,12 +288,17 @@ impl From<GitProgressPhase> for MobileHomePhase {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct MobileHomeProgress {
|
pub struct MobileHomeProgress {
|
||||||
|
action: Option<MobileHomeActionKind>,
|
||||||
phase: MobileHomePhase,
|
phase: MobileHomePhase,
|
||||||
title: String,
|
title: String,
|
||||||
detail: String,
|
detail: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MobileHomeProgress {
|
impl MobileHomeProgress {
|
||||||
|
pub fn action(&self) -> Option<MobileHomeActionKind> {
|
||||||
|
self.action
|
||||||
|
}
|
||||||
|
|
||||||
pub fn phase(&self) -> MobileHomePhase {
|
pub fn phase(&self) -> MobileHomePhase {
|
||||||
self.phase
|
self.phase
|
||||||
}
|
}
|
||||||
@@ -264,32 +313,34 @@ impl MobileHomeProgress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct MobileHomeOperation {
|
pub struct MobileHomeOperation {
|
||||||
|
authentication: Arc<MobileAuthentication>,
|
||||||
control: GitOperationControl,
|
control: GitOperationControl,
|
||||||
phase: Arc<Mutex<MobileHomePhase>>,
|
phase: Arc<Mutex<MobileHomePhase>>,
|
||||||
|
action: Mutex<Option<MobileHomeActionKind>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MobileHomeOperation {
|
impl MobileHomeOperation {
|
||||||
fn default() -> Self {
|
pub fn new(authentication: Arc<MobileAuthentication>) -> Self {
|
||||||
let phase = Arc::new(Mutex::new(MobileHomePhase::Validating));
|
let phase = Arc::new(Mutex::new(MobileHomePhase::Validating));
|
||||||
let observed = Arc::clone(&phase);
|
let observed = Arc::clone(&phase);
|
||||||
Self {
|
Self {
|
||||||
|
authentication,
|
||||||
control: GitOperationControl::new(move |phase| {
|
control: GitOperationControl::new(move |phase| {
|
||||||
if let Ok(mut current) = observed.lock() {
|
if let Ok(mut current) = observed.lock() {
|
||||||
*current = phase.into();
|
*current = phase.into();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
phase,
|
phase,
|
||||||
|
action: Mutex::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl MobileHomeOperation {
|
|
||||||
pub fn cancel(&self) {
|
pub fn cancel(&self) {
|
||||||
self.control.cancel();
|
self.control.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn progress(&self) -> MobileHomeProgress {
|
pub fn progress(&self) -> MobileHomeProgress {
|
||||||
progress_copy(
|
progress_copy(
|
||||||
|
self.action.lock().ok().and_then(|action| *action),
|
||||||
self.phase
|
self.phase
|
||||||
.lock()
|
.lock()
|
||||||
.map_or(MobileHomePhase::Validating, |phase| *phase),
|
.map_or(MobileHomePhase::Validating, |phase| *phase),
|
||||||
@@ -320,15 +371,53 @@ impl MobileHomeOperation {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
storage.refresh(now, &self.control)
|
self.run(MobileHomeActionKind::Fetch, || {
|
||||||
|
storage.fetch(now, &self.control)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh(&self) -> Result<MobileHomePage, MobileHomeError> {
|
pub fn commit(&self, message: &str) -> Result<MobileHomePage, MobileHomeError> {
|
||||||
MobileHomeStorage::load()?.refresh(unix_seconds()?, &self.control)
|
if message.trim().is_empty() || message.contains('\0') {
|
||||||
|
return Err(MobileHomeError::invalid_commit_message());
|
||||||
|
}
|
||||||
|
self.run(MobileHomeActionKind::Commit, || {
|
||||||
|
MobileHomeStorage::load()?.commit(message, &self.control)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch(&self) -> Result<MobileHomePage, MobileHomeError> {
|
||||||
|
self.run(MobileHomeActionKind::Fetch, || {
|
||||||
|
MobileHomeStorage::load()?.fetch(unix_seconds()?, &self.control)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pull(&self) -> Result<MobileHomePage, MobileHomeError> {
|
pub fn pull(&self) -> Result<MobileHomePage, MobileHomeError> {
|
||||||
|
self.run(MobileHomeActionKind::Pull, || {
|
||||||
MobileHomeStorage::load()?.pull(unix_seconds()?, &self.control)
|
MobileHomeStorage::load()?.pull(unix_seconds()?, &self.control)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&self) -> Result<MobileHomePage, MobileHomeError> {
|
||||||
|
self.run(MobileHomeActionKind::Push, || {
|
||||||
|
MobileHomeStorage::load()?.push(unix_seconds()?, &self.control)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
action: MobileHomeActionKind,
|
||||||
|
operation: impl FnOnce() -> Result<MobileHomePage, MobileHomeError>,
|
||||||
|
) -> Result<MobileHomePage, MobileHomeError> {
|
||||||
|
if let Ok(mut current) = self.action.lock() {
|
||||||
|
*current = Some(action);
|
||||||
|
}
|
||||||
|
let reservation = self
|
||||||
|
.authentication
|
||||||
|
.reserve_repository_operation(repository_operation(action))
|
||||||
|
.map_err(MobileHomeError::from_reservation)?;
|
||||||
|
let result = operation();
|
||||||
|
drop(reservation);
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,27 +445,50 @@ impl MobileHomeStorage {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh(
|
fn commit(
|
||||||
|
&self,
|
||||||
|
message: &str,
|
||||||
|
control: &GitOperationControl,
|
||||||
|
) -> Result<MobileHomePage, MobileHomeError> {
|
||||||
|
control
|
||||||
|
.report(GitProgressPhase::Validating)
|
||||||
|
.map_err(MobileHomeError::from_git)?;
|
||||||
|
let commit = self
|
||||||
|
.git
|
||||||
|
.commit_all(message)
|
||||||
|
.map_err(MobileHomeError::from_git)?;
|
||||||
|
control
|
||||||
|
.report(GitProgressPhase::Refreshing)
|
||||||
|
.map_err(MobileHomeError::from_git)?;
|
||||||
|
self.page(
|
||||||
|
MobileHomeFreshness::Cached,
|
||||||
|
self.config.mobile_home_refreshed_at(),
|
||||||
|
Some(commit_notice(&commit)),
|
||||||
|
)
|
||||||
|
.map_err(MobileHomeError::after_commit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch(
|
||||||
&self,
|
&self,
|
||||||
now: i64,
|
now: i64,
|
||||||
control: &GitOperationControl,
|
control: &GitOperationControl,
|
||||||
) -> Result<MobileHomePage, MobileHomeError> {
|
) -> Result<MobileHomePage, MobileHomeError> {
|
||||||
let store = self.credentials()?;
|
let store = self.credentials()?;
|
||||||
let refreshed = self.git.fetch_with_transport_controlled(
|
let fetched = self.git.fetch_with_transport_controlled(
|
||||||
&self.remote,
|
&self.remote,
|
||||||
&store,
|
&store,
|
||||||
&crate::git::EmbeddedFetchTransport,
|
&crate::git::EmbeddedFetchTransport,
|
||||||
control,
|
control,
|
||||||
);
|
);
|
||||||
let locked = store.lock();
|
let locked = store.lock();
|
||||||
refreshed.map_err(MobileHomeError::from_git)?;
|
let outcome = fetched.map_err(MobileHomeError::from_git)?;
|
||||||
if let Err(error) = locked {
|
if let Err(error) = locked {
|
||||||
return Err(MobileHomeError::partial_secret(error));
|
return Err(MobileHomeError::partial_secret(error));
|
||||||
}
|
}
|
||||||
control
|
control
|
||||||
.report(GitProgressPhase::Refreshing)
|
.report(GitProgressPhase::Refreshing)
|
||||||
.map_err(MobileHomeError::from_git)?;
|
.map_err(MobileHomeError::from_git)?;
|
||||||
self.current_page(now, None)
|
self.current_page(now, Some(fetch_notice(&outcome)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pull(
|
fn pull(
|
||||||
@@ -404,6 +516,31 @@ impl MobileHomeStorage {
|
|||||||
.map_err(MobileHomeError::after_pull)
|
.map_err(MobileHomeError::after_pull)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push(
|
||||||
|
&self,
|
||||||
|
now: i64,
|
||||||
|
control: &GitOperationControl,
|
||||||
|
) -> Result<MobileHomePage, MobileHomeError> {
|
||||||
|
let store = self.credentials()?;
|
||||||
|
let pushed = self.git.push_with_transport_controlled(
|
||||||
|
&self.remote,
|
||||||
|
None,
|
||||||
|
&store,
|
||||||
|
&crate::git::ReqwestGitTransport,
|
||||||
|
control,
|
||||||
|
);
|
||||||
|
let locked = store.lock();
|
||||||
|
let outcome = pushed.map_err(MobileHomeError::from_git)?;
|
||||||
|
if let Err(error) = locked {
|
||||||
|
return Err(MobileHomeError::partial_secret(error));
|
||||||
|
}
|
||||||
|
control
|
||||||
|
.report(GitProgressPhase::Refreshing)
|
||||||
|
.map_err(MobileHomeError::from_git)?;
|
||||||
|
self.current_page(now, Some(push_notice(&outcome)))
|
||||||
|
.map_err(MobileHomeError::after_push)
|
||||||
|
}
|
||||||
|
|
||||||
fn credentials(&self) -> Result<NativeSecretStore, MobileHomeError> {
|
fn credentials(&self) -> Result<NativeSecretStore, MobileHomeError> {
|
||||||
let store = NativeSecretStore::system(
|
let store = NativeSecretStore::system(
|
||||||
SecretCachePolicy::Disabled,
|
SecretCachePolicy::Disabled,
|
||||||
@@ -463,6 +600,7 @@ pub enum MobileHomeErrorKind {
|
|||||||
Authentication,
|
Authentication,
|
||||||
Conflict,
|
Conflict,
|
||||||
DirtyLocalChanges,
|
DirtyLocalChanges,
|
||||||
|
NoChanges,
|
||||||
Offline,
|
Offline,
|
||||||
Interrupted,
|
Interrupted,
|
||||||
SecureStorage,
|
SecureStorage,
|
||||||
@@ -557,6 +695,17 @@ impl MobileHomeError {
|
|||||||
title: "Local Changes Need Attention",
|
title: "Local Changes Need Attention",
|
||||||
detail: "Commit or discard local changes before pulling remote activity.".to_owned(),
|
detail: "Commit or discard local changes before pulling remote activity.".to_owned(),
|
||||||
},
|
},
|
||||||
|
GitError::NoChanges => Self {
|
||||||
|
kind: MobileHomeErrorKind::NoChanges,
|
||||||
|
title: "Nothing to Commit",
|
||||||
|
detail: "The password-store working tree has no changes to commit.".to_owned(),
|
||||||
|
},
|
||||||
|
GitError::NonFastForward => Self {
|
||||||
|
kind: MobileHomeErrorKind::Conflict,
|
||||||
|
title: "Pull Before Pushing",
|
||||||
|
detail: "The remote branch contains commits that are not local. Pull and resolve any conflicts before retrying Push."
|
||||||
|
.to_owned(),
|
||||||
|
},
|
||||||
GitError::NetworkUnavailable => Self {
|
GitError::NetworkUnavailable => Self {
|
||||||
kind: MobileHomeErrorKind::Offline,
|
kind: MobileHomeErrorKind::Offline,
|
||||||
title: "Server Is Offline",
|
title: "Server Is Offline",
|
||||||
@@ -589,6 +738,43 @@ impl MobileHomeError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn invalid_commit_message() -> Self {
|
||||||
|
Self {
|
||||||
|
kind: MobileHomeErrorKind::NoChanges,
|
||||||
|
title: "Commit Message Required",
|
||||||
|
detail: "Enter a non-empty commit message and try again.".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_reservation(error: MobileRepositoryOperationError) -> Self {
|
||||||
|
match error {
|
||||||
|
MobileRepositoryOperationError::DirtyEditor => Self {
|
||||||
|
kind: MobileHomeErrorKind::DirtyLocalChanges,
|
||||||
|
title: "Unsaved Entry Edits",
|
||||||
|
detail: "Save or discard the open entry editor before pulling or pushing."
|
||||||
|
.to_owned(),
|
||||||
|
},
|
||||||
|
MobileRepositoryOperationError::OpenEditor => Self {
|
||||||
|
kind: MobileHomeErrorKind::DirtyLocalChanges,
|
||||||
|
title: "Close the Entry Editor",
|
||||||
|
detail: "Close the open entry editor before pulling so its source cannot change underneath it."
|
||||||
|
.to_owned(),
|
||||||
|
},
|
||||||
|
MobileRepositoryOperationError::Busy => Self {
|
||||||
|
kind: MobileHomeErrorKind::Interrupted,
|
||||||
|
title: "Password-Store Action in Progress",
|
||||||
|
detail: "Wait for the current password-store action to finish, then retry."
|
||||||
|
.to_owned(),
|
||||||
|
},
|
||||||
|
MobileRepositoryOperationError::Unavailable => Self {
|
||||||
|
kind: MobileHomeErrorKind::Repository,
|
||||||
|
title: "Password-Store State Is Unavailable",
|
||||||
|
detail: "The shared mobile repository state could not be locked. Restart IronStorage and retry."
|
||||||
|
.to_owned(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn from_secret(error: SecretStoreError) -> Self {
|
fn from_secret(error: SecretStoreError) -> Self {
|
||||||
let detail = match error {
|
let detail = match error {
|
||||||
SecretStoreError::Denied => "Access to the application token was denied.",
|
SecretStoreError::Denied => "Access to the application token was denied.",
|
||||||
@@ -631,6 +817,24 @@ impl MobileHomeError {
|
|||||||
.to_owned(),
|
.to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn after_commit(_error: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: MobileHomeErrorKind::PartialProgress,
|
||||||
|
title: "Commit Completed, Status Unavailable",
|
||||||
|
detail: "The commit was created, but Home could not rebuild the current Git status. Reload Home to retry."
|
||||||
|
.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_push(_error: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: MobileHomeErrorKind::PartialProgress,
|
||||||
|
title: "Push Completed, Status Unavailable",
|
||||||
|
detail: "The remote branch was updated, but Home could not rebuild the current Git status. Reload Home to retry."
|
||||||
|
.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for MobileHomeError {
|
impl fmt::Display for MobileHomeError {
|
||||||
@@ -674,6 +878,11 @@ fn page_from_divergence(
|
|||||||
title: format!("{}/{}", divergence.remote().name(), divergence.branch()),
|
title: format!("{}/{}", divergence.remote().name(), divergence.branch()),
|
||||||
detail: "Tracked HTTPS branch".to_owned(),
|
detail: "Tracked HTTPS branch".to_owned(),
|
||||||
system_image: "point.3.connected.trianglepath.dotted".to_owned(),
|
system_image: "point.3.connected.trianglepath.dotted".to_owned(),
|
||||||
|
actions: vec![
|
||||||
|
mobile_action(MobileHomeActionKind::Fetch),
|
||||||
|
mobile_action(MobileHomeActionKind::Pull),
|
||||||
|
mobile_action(MobileHomeActionKind::Push),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
MobileHomeSummaryRow {
|
MobileHomeSummaryRow {
|
||||||
id: "divergence".to_owned(),
|
id: "divergence".to_owned(),
|
||||||
@@ -684,6 +893,13 @@ fn page_from_divergence(
|
|||||||
} else {
|
} else {
|
||||||
"arrow.triangle.2.circlepath".to_owned()
|
"arrow.triangle.2.circlepath".to_owned()
|
||||||
},
|
},
|
||||||
|
actions: [
|
||||||
|
(behind > 0).then(|| mobile_action(MobileHomeActionKind::Pull)),
|
||||||
|
(ahead > 0).then(|| mobile_action(MobileHomeActionKind::Push)),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect(),
|
||||||
},
|
},
|
||||||
MobileHomeSummaryRow {
|
MobileHomeSummaryRow {
|
||||||
id: "working-tree".to_owned(),
|
id: "working-tree".to_owned(),
|
||||||
@@ -702,6 +918,11 @@ fn page_from_divergence(
|
|||||||
} else {
|
} else {
|
||||||
"exclamationmark.triangle".to_owned()
|
"exclamationmark.triangle".to_owned()
|
||||||
},
|
},
|
||||||
|
actions: if local_count > 0 {
|
||||||
|
vec![mobile_action(MobileHomeActionKind::Commit)]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
MobileHomePage {
|
MobileHomePage {
|
||||||
@@ -765,6 +986,11 @@ fn mobile_commit(activity: &GitCommitActivity, incoming: bool) -> MobileHomeComm
|
|||||||
},
|
},
|
||||||
timestamp: commit.timestamp(),
|
timestamp: commit.timestamp(),
|
||||||
changes,
|
changes,
|
||||||
|
actions: vec![mobile_action(if incoming {
|
||||||
|
MobileHomeActionKind::Pull
|
||||||
|
} else {
|
||||||
|
MobileHomeActionKind::Push
|
||||||
|
})],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,23 +1107,113 @@ fn pull_notice(outcome: PullOutcome) -> MobileHomeNotice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn progress_copy(phase: MobileHomePhase) -> MobileHomeProgress {
|
fn commit_notice(commit: &str) -> MobileHomeNotice {
|
||||||
let (title, detail) = match phase {
|
MobileHomeNotice {
|
||||||
MobileHomePhase::Validating => ("Checking Home", "Validating local and remote Git state."),
|
title: "Changes Committed".to_owned(),
|
||||||
MobileHomePhase::Authenticating => (
|
detail: format!("Created commit {}.", &commit[..commit.len().min(12)]),
|
||||||
|
system_image: "checkmark.circle".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_notice(outcome: &FetchOutcome) -> MobileHomeNotice {
|
||||||
|
MobileHomeNotice {
|
||||||
|
title: if outcome.received_pack() {
|
||||||
|
"Remote Status Updated"
|
||||||
|
} else {
|
||||||
|
"Remote Already Current"
|
||||||
|
}
|
||||||
|
.to_owned(),
|
||||||
|
detail: if outcome.received_pack() {
|
||||||
|
format!(
|
||||||
|
"Received new objects from {} without changing the local password store.",
|
||||||
|
outcome.remote()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("{} has no new objects for this clone.", outcome.remote())
|
||||||
|
},
|
||||||
|
system_image: "arrow.clockwise.circle".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_notice(outcome: &PushOutcome) -> MobileHomeNotice {
|
||||||
|
let unchanged = outcome.old() == Some(outcome.new_id());
|
||||||
|
MobileHomeNotice {
|
||||||
|
title: if unchanged {
|
||||||
|
"Remote Already Current"
|
||||||
|
} else {
|
||||||
|
"Changes Pushed"
|
||||||
|
}
|
||||||
|
.to_owned(),
|
||||||
|
detail: if unchanged {
|
||||||
|
format!(
|
||||||
|
"{}/{} already points to the local commit.",
|
||||||
|
outcome.remote(),
|
||||||
|
outcome.branch()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"Updated {}/{} with local commits.",
|
||||||
|
outcome.remote(),
|
||||||
|
outcome.branch()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
system_image: "arrow.up.circle".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mobile_action(kind: MobileHomeActionKind) -> MobileHomeAction {
|
||||||
|
let (title, system_image) = match kind {
|
||||||
|
MobileHomeActionKind::Commit => ("Commit", "checkmark.circle"),
|
||||||
|
MobileHomeActionKind::Fetch => ("Fetch", "arrow.clockwise"),
|
||||||
|
MobileHomeActionKind::Pull => ("Pull", "arrow.down.circle"),
|
||||||
|
MobileHomeActionKind::Push => ("Push", "arrow.up.circle"),
|
||||||
|
};
|
||||||
|
MobileHomeAction {
|
||||||
|
kind,
|
||||||
|
title,
|
||||||
|
system_image,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repository_operation(action: MobileHomeActionKind) -> MobileRepositoryOperation {
|
||||||
|
match action {
|
||||||
|
MobileHomeActionKind::Commit => MobileRepositoryOperation::Commit,
|
||||||
|
MobileHomeActionKind::Fetch => MobileRepositoryOperation::Fetch,
|
||||||
|
MobileHomeActionKind::Pull => MobileRepositoryOperation::Pull,
|
||||||
|
MobileHomeActionKind::Push => MobileRepositoryOperation::Push,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn progress_copy(
|
||||||
|
action: Option<MobileHomeActionKind>,
|
||||||
|
phase: MobileHomePhase,
|
||||||
|
) -> MobileHomeProgress {
|
||||||
|
let (title, detail) = match (action, phase) {
|
||||||
|
(Some(MobileHomeActionKind::Commit), MobileHomePhase::Validating) => (
|
||||||
|
"Committing Changes",
|
||||||
|
"Staging current password-store changes and creating the commit in Rust.",
|
||||||
|
),
|
||||||
|
(_, MobileHomePhase::Validating) => {
|
||||||
|
("Checking Home", "Validating local and remote Git state.")
|
||||||
|
}
|
||||||
|
(_, MobileHomePhase::Authenticating) => (
|
||||||
"Authenticating",
|
"Authenticating",
|
||||||
"Reading the application token from protected storage.",
|
"Reading the application token from protected storage.",
|
||||||
),
|
),
|
||||||
MobileHomePhase::Receiving => ("Refreshing Remote", "Receiving remote Git objects."),
|
(_, MobileHomePhase::Receiving) => ("Refreshing Remote", "Receiving remote Git objects."),
|
||||||
MobileHomePhase::Integrating => (
|
(_, MobileHomePhase::Integrating) => (
|
||||||
"Updating Password Store",
|
"Updating Password Store",
|
||||||
"Integrating fetched commits into the local clone.",
|
"Integrating fetched commits into the local clone.",
|
||||||
),
|
),
|
||||||
MobileHomePhase::Finishing => {
|
(_, MobileHomePhase::Sending) => {
|
||||||
|
("Pushing Changes", "Sending local Git objects over HTTPS.")
|
||||||
|
}
|
||||||
|
(_, MobileHomePhase::Finishing) => {
|
||||||
("Updating Home", "Building current activity and divergence.")
|
("Updating Home", "Building current activity and divergence.")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
MobileHomeProgress {
|
MobileHomeProgress {
|
||||||
|
action,
|
||||||
phase,
|
phase,
|
||||||
title: title.to_owned(),
|
title: title.to_owned(),
|
||||||
detail: detail.to_owned(),
|
detail: detail.to_owned(),
|
||||||
@@ -935,8 +1251,8 @@ mod tests {
|
|||||||
use crate::git::{GitChangeKind, GitError};
|
use crate::git::{GitChangeKind, GitError};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
MobileHomeChangeKind, MobileHomeChangeStatus, MobileHomeErrorKind, display_text, is_stale,
|
MobileHomeActionKind, MobileHomeChangeKind, MobileHomeChangeStatus, MobileHomeErrorKind,
|
||||||
mobile_change,
|
MobileHomePhase, display_text, is_stale, mobile_action, mobile_change, progress_copy,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -974,6 +1290,20 @@ mod tests {
|
|||||||
assert_eq!(offline.kind(), MobileHomeErrorKind::Offline);
|
assert_eq!(offline.kind(), MobileHomeErrorKind::Offline);
|
||||||
let dirty = super::MobileHomeError::from_git(GitError::DirtyWorktree);
|
let dirty = super::MobileHomeError::from_git(GitError::DirtyWorktree);
|
||||||
assert_eq!(dirty.kind(), MobileHomeErrorKind::DirtyLocalChanges);
|
assert_eq!(dirty.kind(), MobileHomeErrorKind::DirtyLocalChanges);
|
||||||
|
let push_conflict = super::MobileHomeError::from_git(GitError::NonFastForward);
|
||||||
|
assert_eq!(push_conflict.kind(), MobileHomeErrorKind::Conflict);
|
||||||
|
let no_changes = super::MobileHomeError::from_git(GitError::NoChanges);
|
||||||
|
assert_eq!(no_changes.kind(), MobileHomeErrorKind::NoChanges);
|
||||||
assert!(!offline.to_string().contains("token-value"));
|
assert!(!offline.to_string().contains("token-value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn actions_and_progress_are_typed_before_swift_presentation() {
|
||||||
|
let push = mobile_action(MobileHomeActionKind::Push);
|
||||||
|
assert_eq!(push.title(), "Push");
|
||||||
|
assert_eq!(push.system_image(), "arrow.up.circle");
|
||||||
|
let progress = progress_copy(Some(MobileHomeActionKind::Push), MobileHomePhase::Sending);
|
||||||
|
assert_eq!(progress.action(), Some(MobileHomeActionKind::Push));
|
||||||
|
assert_eq!(progress.title(), "Pushing Changes");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,23 @@ fn local_git_workflow_stages_commits_diffs_logs_and_deletes() -> TestResult {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn user_commit_stages_every_current_change_in_storage() -> TestResult {
|
||||||
|
let temporary = tempfile::tempdir()?;
|
||||||
|
let store = Repository::open(temporary.path())?;
|
||||||
|
let git = GitRepository::init(&store, identity())?;
|
||||||
|
fs::write(temporary.path().join(".gpg-id"), b"ALICE\n")?;
|
||||||
|
fs::write(temporary.path().join("mail.gpg"), b"ciphertext")?;
|
||||||
|
|
||||||
|
let commit = git.commit_all("Save mobile changes.")?;
|
||||||
|
|
||||||
|
assert_eq!(commit.len(), 40);
|
||||||
|
assert!(git.status()?.is_clean());
|
||||||
|
assert_eq!(git.log(Some(1))?[0].message(), "Save mobile changes.");
|
||||||
|
assert_eq!(git.commit_all("Nothing changed"), Err(GitError::NoChanges));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_repository_selection_is_innermost() -> TestResult {
|
fn nested_repository_selection_is_innermost() -> TestResult {
|
||||||
let temporary = tempfile::tempdir()?;
|
let temporary = tempfile::tempdir()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user