Skip to content

Commit e0e80c8

Browse files
authored
Dynamic constraint feature (#310)
1 parent 7392348 commit e0e80c8

17 files changed

+406
-369
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
let name = operation.name ?? ""
21+
let queueName = queue.name ?? ""
22+
assertNotEmptyString(name)
23+
assertNotEmptyString(queueName)
24+
persister.put(queueName: queueName, taskId: name, data: data)
25+
}
26+
27+
func remove(queueName: String, taskId: String) {
28+
persister.remove(queueName: queueName, taskId: taskId)
29+
}
30+
31+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// Created by Lucas Nelaupe on 26/5/20.
3+
//
4+
5+
import Foundation
6+
7+
internal final class TagConstraint: SimpleConstraint, CodableConstraint {
8+
9+
internal var tags: Set<String>
10+
11+
required init(tags: Set<String>) {
12+
self.tags = tags
13+
}
14+
15+
convenience init?(from decoder: Decoder) throws {
16+
let container = try decoder.container(keyedBy: TagConstraintKey.self)
17+
if container.contains(.tags) {
18+
try self.init(tags: container.decode(Set<String>.self, forKey: .tags))
19+
} else { return nil }
20+
}
21+
22+
func insert(tag: String) {
23+
tags.insert(tag)
24+
}
25+
26+
func contains(tag: String) -> Bool {
27+
return tags.contains(tag)
28+
}
29+
30+
private enum TagConstraintKey: String, CodingKey {
31+
case tags
32+
}
33+
34+
func encode(to encoder: Encoder) throws {
35+
var container = encoder.container(keyedBy: TagConstraintKey.self)
36+
try container.encode(tags, forKey: .tags)
37+
}
38+
}

Sources/SwiftQueue/Constraint.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public protocol JobConstraint {
4444

4545
}
4646

47-
protocol CodableConstraint: Encodable {
47+
public protocol CodableConstraint: Encodable {
4848

4949
/**
5050
Build constraint when deserialize
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2019 Lucas Nelaupe
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
import Foundation
24+
25+
public protocol ConstraintMaker {
26+
27+
func make(from decoder: Decoder) throws -> [JobConstraint]
28+
29+
}
30+
31+
class DefaultConstraintMaker: ConstraintMaker {
32+
33+
func make(from decoder: Decoder) throws -> [JobConstraint] {
34+
var constraints: [JobConstraint] = []
35+
36+
#if os(iOS)
37+
if let deadline = try BatteryChargingConstraint(from: decoder) { constraints.append(deadline) }
38+
#endif
39+
40+
if let constraint = try DeadlineConstraint(from: decoder) { constraints.append(constraint) }
41+
if let constraint = try DelayConstraint(from: decoder) { constraints.append(constraint) }
42+
if let constraint = try TimeoutConstraint(from: decoder) { constraints.append(constraint) }
43+
if let constraint = try NetworkConstraint(from: decoder) { constraints.append(constraint) }
44+
if let constraint = try RepeatConstraint(from: decoder) { constraints.append(constraint) }
45+
if let constraint = try JobRetryConstraint(from: decoder) { constraints.append(constraint) }
46+
if let constraint = try UniqueUUIDConstraint(from: decoder) { constraints.append(constraint) }
47+
if let constraint = try TagConstraint(from: decoder) { constraints.append(constraint) }
48+
49+
return constraints
50+
}
51+
52+
}

Sources/SwiftQueue/JobBuilder.swift

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ public final class JobBuilder {
4343
/// If override = true the previous job will be canceled and the new job will be scheduled
4444
public func singleInstance(forId: String, override: Bool = false, includeExecutingJob: Bool = true) -> Self {
4545
assertNotEmptyString(forId)
46-
info.uuid = forId
47-
info.override = override
48-
info.includeExecutingJob = includeExecutingJob
46+
info.constraints.append(UniqueUUIDConstraint(uuid: forId, override: override, includeExecutingJob: includeExecutingJob))
4947
return self
5048
}
5149

@@ -62,14 +60,14 @@ public final class JobBuilder {
6260
/// Otherwise it will wait for the remaining time
6361
public func delay(time: TimeInterval) -> Self {
6462
assert(time >= 0)
65-
info.delay = time
63+
info.constraints.append(DelayConstraint(delay: time))
6664
return self
6765
}
6866

6967
/// If the job hasn't run after the date, It will be removed
7068
/// will call onRemove(SwiftQueueError.deadline)
7169
public func deadline(date: Date) -> Self {
72-
info.deadline = date
70+
info.constraints.append(DeadlineConstraint(deadline: date))
7371
return self
7472
}
7573

@@ -80,47 +78,52 @@ public final class JobBuilder {
8078
public func periodic(limit: Limit = .unlimited, interval: TimeInterval = 0) -> Self {
8179
assert(limit.validate)
8280
assert(interval >= 0)
83-
info.maxRun = limit
84-
info.interval = interval
85-
info.executor = .foreground
81+
info.constraints.append(RepeatConstraint(maxRun: limit, interval: interval, executor: .foreground))
8682
return self
8783
}
8884

8985
@available(iOS 13.0, tvOS 13.0, macOS 10.15, *)
9086
public func periodic(limit: Limit = .unlimited, interval: TimeInterval = 0, executor: Executor = .foreground) -> Self {
9187
assert(limit.validate)
9288
assert(interval >= 0)
93-
info.maxRun = limit
94-
info.interval = interval
95-
info.executor = executor
89+
info.constraints.append(RepeatConstraint(maxRun: limit, interval: interval, executor: executor))
9690
return self
9791
}
9892

9993
/// Connectivity constraint.
10094
public func internet(atLeast: NetworkType) -> Self {
10195
assert(atLeast != .any)
102-
info.requireNetwork = atLeast
96+
info.constraints.append(NetworkConstraint(networkType: atLeast))
10397
return self
10498
}
10599

100+
private var requirePersist = false
101+
106102
/// Job should be persisted.
107103
public func persist() -> Self {
108-
info.isPersisted = true
104+
requirePersist = true
109105
return self
110106
}
111107

112108
/// Limit number of retry. Overall for the lifecycle of the SwiftQueueManager.
113109
/// For a periodic job, the retry count will not be reset at each period.
114110
public func retry(limit: Limit) -> Self {
115111
assert(limit.validate)
116-
info.retries = limit
112+
info.constraints.append(JobRetryConstraint(limit: limit))
117113
return self
118114
}
119115

120116
/// Custom tag to mark the job
121117
public func addTag(tag: String) -> Self {
122-
assertNotEmptyString(tag)
123-
info.tags.insert(tag)
118+
if let constraint: TagConstraint = getConstraint(info.constraints) {
119+
constraint.insert(tag: tag)
120+
return self
121+
}
122+
123+
var set = Set<String>()
124+
set.insert(tag)
125+
126+
info.constraints.append(TagConstraint(tags: set))
124127
return self
125128
}
126129

@@ -142,15 +145,15 @@ public final class JobBuilder {
142145
return self
143146
}
144147

145-
/// Call if the job can only run when the device is charging
148+
/// Call if job can only run when the device is charging
146149
public func requireCharging() -> Self {
147-
info.requireCharging = true
150+
info.constraints.append(BatteryChargingConstraint())
148151
return self
149152
}
150153

151154
/// Maximum time in second that the job is allowed to run
152155
public func timeout(value: TimeInterval) -> Self {
153-
info.timeout = value
156+
info.constraints.append(TimeoutConstraint(timeout: value))
154157
return self
155158
}
156159

@@ -166,11 +169,16 @@ public final class JobBuilder {
166169

167170
/// Add job to the JobQueue
168171
public func schedule(manager: SwiftQueueManager) {
169-
if info.isPersisted {
170-
// Check if we will be able to serialize args
172+
if requirePersist {
173+
let constraint: UniqueUUIDConstraint? = getConstraint(info)
174+
if constraint == nil {
175+
info.constraints.append(UniqueUUIDConstraint(uuid: UUID().uuidString, override: false, includeExecutingJob: false))
176+
}
171177
assert(JSONSerialization.isValidJSONObject(info.params))
178+
info.constraints.append(PersisterConstraint(serializer: manager.params.serializer, persister: manager.params.persister))
172179
}
173180

174181
manager.enqueue(info: info)
175182
}
183+
176184
}

0 commit comments

Comments
 (0)