Skip to content

Commit f4ad580

Browse files
author
Lucas Nelaupe
committed
Merge commit 'bc1313d3266c77d6e83da1d5294eb9a35e9fba6f'
1 parent b96454d commit f4ad580

29 files changed

+681
-761
lines changed

Sources/SwiftQueue/Constraint+Charging.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ internal final class BatteryChargingConstraint: JobConstraint {
5858
return true
5959
}
6060

61-
operation.logger.log(.verbose, jobId: operation.info.uuid, message: "Unsatisfied charging requirement")
61+
operation.logger.log(.verbose, jobId: operation.name, message: "Unsatisfied charging requirement")
6262

6363
/// Keep actual job
6464
actual = operation
@@ -73,6 +73,6 @@ internal final class BatteryChargingConstraint: JobConstraint {
7373

7474
#else
7575

76-
internal final class BatteryChargingConstraint: DefaultNoConstraint {}
76+
internal final class BatteryChargingConstraint: SimpleConstraint {}
7777

7878
#endif

Sources/SwiftQueue/Constraint+Deadline.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import Foundation
2424

2525
internal final class DeadlineConstraint: JobConstraint {
2626

27-
private let deadline: Date
27+
/// Cancel the job after a certain date
28+
internal let deadline: Date
2829

2930
init(deadline: Date) {
3031
self.deadline = deadline
@@ -53,4 +54,20 @@ internal final class DeadlineConstraint: JobConstraint {
5354
throw SwiftQueueError.deadline
5455
}
5556
}
57+
58+
private enum DeadlineConstraintKey: String, CodingKey {
59+
case deadline = "deadline"
60+
}
61+
62+
func decode(builder: ConstraintBuilder, from decoder: Decoder) throws {
63+
let container = try decoder.container(keyedBy: DeadlineConstraintKey.self)
64+
let deadline: Date = try container.decode(Date.self, forKey: .deadline)
65+
builder.register(constraint: DeadlineConstraint(deadline: deadline))
66+
}
67+
68+
func encode(to encoder: Encoder) throws {
69+
var container = encoder.container(keyedBy: DeadlineConstraintKey.self)
70+
try container.encode(deadline, forKey: .deadline)
71+
}
72+
5673
}

Sources/SwiftQueue/Constraint+Delay.swift

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,16 @@
2222

2323
import Foundation
2424

25-
internal final class DelayConstraint: JobConstraint {
25+
internal final class DelayConstraint: SimpleConstraint {
2626

27-
private let delay: TimeInterval
27+
/// Delay for the first execution of the job
28+
internal let delay: TimeInterval
2829

2930
init(delay: TimeInterval) {
3031
self.delay = delay
3132
}
3233

33-
func willSchedule(queue: SqOperationQueue, operation: SqOperation) throws {
34-
// Nothing to do
35-
}
36-
37-
func willRun(operation: SqOperation) throws {
38-
// Nothing to do
39-
}
40-
41-
func run(operation: SqOperation) -> Bool {
34+
override func run(operation: SqOperation) -> Bool {
4235
let epoch = Date().timeIntervalSince(operation.info.createTime)
4336
guard epoch < delay else {
4437
// Epoch already greater than delay
@@ -55,7 +48,23 @@ internal final class DelayConstraint: JobConstraint {
5548
operation?.run()
5649
})
5750

58-
operation.logger.log(.verbose, jobId: operation.info.uuid, message: "Job delayed by \(time)s")
51+
operation.logger.log(.verbose, jobId: operation.name, message: "Job delayed by \(time)s")
5952
return false
6053
}
54+
55+
private enum delayConstraintKey: String, CodingKey {
56+
case delay = "delay"
57+
}
58+
59+
override func decode(builder: ConstraintBuilder, from decoder: Decoder) throws {
60+
let container = try decoder.container(keyedBy: delayConstraintKey.self)
61+
let delay: TimeInterval = try container.decode(TimeInterval.self, forKey: .delay)
62+
builder.register(constraint: DelayConstraint(delay: delay))
63+
}
64+
65+
override func encode(to encoder: Encoder) throws {
66+
var container = encoder.container(keyedBy: delayConstraintKey.self)
67+
try container.encode(delay, forKey: .delay)
68+
}
69+
6170
}

Sources/SwiftQueue/Constraint+Executor.swift

Lines changed: 0 additions & 46 deletions
This file was deleted.

Sources/SwiftQueue/Constraint+Network.swift

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,43 +38,48 @@ public enum NetworkType: Int, Codable {
3838
#if os(iOS) || os(macOS) || os(tvOS)
3939
internal final class NetworkConstraint: JobConstraint {
4040

41-
var reachability: Reachability?
41+
/// Require a certain connectivity type
42+
internal let networkType: NetworkType
43+
44+
private var reachability: Reachability!
45+
46+
init(networkType: NetworkType) {
47+
self.networkType = networkType
48+
}
4249

4350
func willSchedule(queue: SqOperationQueue, operation: SqOperation) throws {
44-
if operation.info.requireNetwork.rawValue > NetworkType.any.rawValue {
45-
do {
46-
try self.reachability = Reachability(targetQueue: operation.dispatchQueue, notificationQueue: operation.dispatchQueue)
47-
} catch {
48-
operation.logger.log(.error, jobId: operation.info.uuid, message: error.localizedDescription)
49-
}
50-
}
51+
assert(operation.dispatchQueue != .main)
52+
self.reachability = try Reachability(targetQueue: operation.dispatchQueue, notificationQueue: operation.dispatchQueue)
5153
}
5254

5355
func willRun(operation: SqOperation) throws {
54-
guard let reachability = reachability else { return }
55-
guard hasCorrectNetwork(reachability: reachability, required: operation.info.requireNetwork) else {
56+
guard hasCorrectNetwork(reachability: reachability, required: networkType) else {
5657
try reachability.startNotifier()
5758
return
5859
}
5960
}
6061

6162
func run(operation: SqOperation) -> Bool {
62-
guard let reachability = reachability else {
63-
return true
64-
}
63+
guard hasCorrectNetwork(reachability: reachability, required: networkType) else {
64+
reachability.whenReachable = { reachability in
65+
reachability.stopNotifier()
66+
reachability.whenReachable = nil
67+
operation.run()
68+
}
6569

66-
if hasCorrectNetwork(reachability: reachability, required: operation.info.requireNetwork) {
67-
return true
68-
}
70+
operation.logger.log(.verbose, jobId: operation.name, message: "Unsatisfied network requirement")
71+
72+
do {
73+
try reachability.startNotifier()
74+
} catch {
75+
operation.logger.log(.verbose, jobId: operation.name, message: "Unable to start network listener. Job will run.")
76+
operation.logger.log(.error, jobId: operation.name, message: error.localizedDescription)
77+
}
6978

70-
reachability.whenReachable = { reachability in
71-
reachability.stopNotifier()
72-
reachability.whenReachable = nil
73-
operation.run()
79+
return false
7480
}
7581

76-
operation.logger.log(.verbose, jobId: operation.info.uuid, message: "Unsatisfied network requirement")
77-
return false
82+
return true
7883
}
7984

8085
private func hasCorrectNetwork(reachability: Reachability, required: NetworkType) -> Bool {
@@ -88,9 +93,24 @@ internal final class NetworkConstraint: JobConstraint {
8893
}
8994
}
9095

96+
private enum networkConstraintKey: String, CodingKey {
97+
case requireNetwork = "requireNetwork"
98+
}
99+
100+
func decode(builder: ConstraintBuilder, from decoder: Decoder) throws {
101+
let container = try decoder.container(keyedBy: networkConstraintKey.self)
102+
let networkType: NetworkType = try container.decode(NetworkType.self, forKey: .requireNetwork)
103+
builder.register(constraint: NetworkConstraint(networkType: networkType))
104+
}
105+
106+
func encode(to encoder: Encoder) throws {
107+
var container = encoder.container(keyedBy: networkConstraintKey.self)
108+
try container.encode(networkType, forKey: .requireNetwork)
109+
}
110+
91111
}
92112
#else
93113

94-
internal final class NetworkConstraint: DefaultNoConstraint {}
114+
internal final class NetworkConstraint: SimpleConstraint {}
95115

96116
#endif
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Created by Lucas Nelaupe on 25/5/20.
3+
//
4+
5+
import Foundation
6+
7+
internal class PersisterConstraint: SimpleConstraint {
8+
9+
private let serializer: JobInfoSerializer
10+
11+
private let persister: JobPersister
12+
13+
init(serializer: JobInfoSerializer, persister: JobPersister) {
14+
self.serializer = serializer
15+
self.persister = persister
16+
}
17+
18+
override func willSchedule(queue: SqOperationQueue, operation: SqOperation) throws {
19+
let data = try serializer.serialize(info: operation.info)
20+
persister.put(queueName: queue.name!, taskId: "job.info.uuid", data: data)
21+
}
22+
23+
func remove(queueName: String, taskId: String) {
24+
25+
persister.remove(queueName: queueName, taskId: taskId)
26+
}
27+
28+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//
2+
// Created by Lucas Nelaupe on 26/5/20.
3+
//
4+
5+
import Foundation
6+
7+
internal final class RepeatConstraint: SimpleConstraint {
8+
9+
/// Number of run maximum
10+
public let maxRun: Limit
11+
12+
/// Time between each repetition of the job
13+
public let interval: TimeInterval
14+
15+
/// Executor to run job in foreground or background
16+
public let executor: Executor
17+
18+
/// Current number of run
19+
public var runCount: Double = 0
20+
21+
init(maxRun: Limit, interval: TimeInterval, executor: Executor) {
22+
self.maxRun = maxRun
23+
self.interval = interval
24+
self.executor = executor
25+
}
26+
27+
override func run(operation: SqOperation) -> Bool {
28+
switch executor {
29+
case .background:
30+
return false
31+
case .foreground:
32+
return true
33+
case.any:
34+
return true
35+
}
36+
}
37+
38+
func completionSuccess(sqOperation: SqOperation) {
39+
if case .limited(let limit) = maxRun {
40+
// Reached run limit
41+
guard runCount + 1 < limit else {
42+
sqOperation.onTerminate()
43+
return
44+
}
45+
}
46+
47+
guard interval > 0 else {
48+
// Run immediately
49+
runCount += 1
50+
sqOperation.run()
51+
return
52+
}
53+
54+
// Schedule run after interval
55+
sqOperation.nextRunSchedule = Date().addingTimeInterval(interval)
56+
sqOperation.dispatchQueue.runAfter(interval, callback: {
57+
self.runCount += 1 // TODO weak
58+
sqOperation.run()
59+
})
60+
}
61+
62+
}
63+
64+
/// Enum to specify background and foreground restriction
65+
public enum Executor: Int {
66+
67+
/// Job will only run only when the app is in foreground
68+
case foreground = 0
69+
70+
/// Job will only run only when the app is in background
71+
case background = 1
72+
73+
/// Job can run in both background and foreground
74+
case any = 2
75+
76+
}
77+
78+
internal extension Executor {
79+
80+
static func fromRawValue(value: Int) -> Executor {
81+
assert(value == 0 || value == 1 || value == 2)
82+
switch value {
83+
case 1:
84+
return Executor.background
85+
case 2:
86+
return Executor.any
87+
default:
88+
return Executor.foreground
89+
}
90+
}
91+
92+
}
93+
94+
extension Executor: Codable {
95+
96+
private enum CodingKeys: String, CodingKey { case value }
97+
98+
public init(from decoder: Decoder) throws {
99+
let values = try decoder.container(keyedBy: CodingKeys.self)
100+
let value = try values.decode(Int.self, forKey: .value)
101+
self = Executor.fromRawValue(value: value)
102+
}
103+
104+
public func encode(to encoder: Encoder) throws {
105+
var container = encoder.container(keyedBy: CodingKeys.self)
106+
try container.encode(self.rawValue, forKey: .value)
107+
}
108+
109+
}

0 commit comments

Comments
 (0)