Skip to content

Commit

Permalink
Dynamic constraint feature (#310)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucas34 authored Jun 11, 2020
1 parent 7392348 commit e0e80c8
Show file tree
Hide file tree
Showing 17 changed files with 406 additions and 369 deletions.
31 changes: 31 additions & 0 deletions Sources/SwiftQueue/Constraint+Persister.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Created by Lucas Nelaupe on 25/5/20.
//

import Foundation

internal class PersisterConstraint: SimpleConstraint {

private let serializer: JobInfoSerializer

private let persister: JobPersister

init(serializer: JobInfoSerializer, persister: JobPersister) {
self.serializer = serializer
self.persister = persister
}

override func willSchedule(queue: SqOperationQueue, operation: SqOperation) throws {
let data = try serializer.serialize(info: operation.info)
let name = operation.name ?? ""
let queueName = queue.name ?? ""
assertNotEmptyString(name)
assertNotEmptyString(queueName)
persister.put(queueName: queueName, taskId: name, data: data)
}

func remove(queueName: String, taskId: String) {
persister.remove(queueName: queueName, taskId: taskId)
}

}
38 changes: 38 additions & 0 deletions Sources/SwiftQueue/Constraint+Tag.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Created by Lucas Nelaupe on 26/5/20.
//

import Foundation

internal final class TagConstraint: SimpleConstraint, CodableConstraint {

internal var tags: Set<String>

required init(tags: Set<String>) {
self.tags = tags
}

convenience init?(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TagConstraintKey.self)
if container.contains(.tags) {
try self.init(tags: container.decode(Set<String>.self, forKey: .tags))
} else { return nil }
}

func insert(tag: String) {
tags.insert(tag)
}

func contains(tag: String) -> Bool {
return tags.contains(tag)
}

private enum TagConstraintKey: String, CodingKey {
case tags
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: TagConstraintKey.self)
try container.encode(tags, forKey: .tags)
}
}
2 changes: 1 addition & 1 deletion Sources/SwiftQueue/Constraint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public protocol JobConstraint {

}

protocol CodableConstraint: Encodable {
public protocol CodableConstraint: Encodable {

/**
Build constraint when deserialize
Expand Down
52 changes: 52 additions & 0 deletions Sources/SwiftQueue/ConstraintMaker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// The MIT License (MIT)
//
// Copyright (c) 2019 Lucas Nelaupe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation

public protocol ConstraintMaker {

func make(from decoder: Decoder) throws -> [JobConstraint]

}

class DefaultConstraintMaker: ConstraintMaker {

func make(from decoder: Decoder) throws -> [JobConstraint] {
var constraints: [JobConstraint] = []

#if os(iOS)
if let deadline = try BatteryChargingConstraint(from: decoder) { constraints.append(deadline) }
#endif

if let constraint = try DeadlineConstraint(from: decoder) { constraints.append(constraint) }
if let constraint = try DelayConstraint(from: decoder) { constraints.append(constraint) }
if let constraint = try TimeoutConstraint(from: decoder) { constraints.append(constraint) }
if let constraint = try NetworkConstraint(from: decoder) { constraints.append(constraint) }
if let constraint = try RepeatConstraint(from: decoder) { constraints.append(constraint) }
if let constraint = try JobRetryConstraint(from: decoder) { constraints.append(constraint) }
if let constraint = try UniqueUUIDConstraint(from: decoder) { constraints.append(constraint) }
if let constraint = try TagConstraint(from: decoder) { constraints.append(constraint) }

return constraints
}

}
50 changes: 29 additions & 21 deletions Sources/SwiftQueue/JobBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ public final class JobBuilder {
/// If override = true the previous job will be canceled and the new job will be scheduled
public func singleInstance(forId: String, override: Bool = false, includeExecutingJob: Bool = true) -> Self {
assertNotEmptyString(forId)
info.uuid = forId
info.override = override
info.includeExecutingJob = includeExecutingJob
info.constraints.append(UniqueUUIDConstraint(uuid: forId, override: override, includeExecutingJob: includeExecutingJob))
return self
}

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

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

Expand All @@ -80,47 +78,52 @@ public final class JobBuilder {
public func periodic(limit: Limit = .unlimited, interval: TimeInterval = 0) -> Self {
assert(limit.validate)
assert(interval >= 0)
info.maxRun = limit
info.interval = interval
info.executor = .foreground
info.constraints.append(RepeatConstraint(maxRun: limit, interval: interval, executor: .foreground))
return self
}

@available(iOS 13.0, tvOS 13.0, macOS 10.15, *)
public func periodic(limit: Limit = .unlimited, interval: TimeInterval = 0, executor: Executor = .foreground) -> Self {
assert(limit.validate)
assert(interval >= 0)
info.maxRun = limit
info.interval = interval
info.executor = executor
info.constraints.append(RepeatConstraint(maxRun: limit, interval: interval, executor: executor))
return self
}

/// Connectivity constraint.
public func internet(atLeast: NetworkType) -> Self {
assert(atLeast != .any)
info.requireNetwork = atLeast
info.constraints.append(NetworkConstraint(networkType: atLeast))
return self
}

private var requirePersist = false

/// Job should be persisted.
public func persist() -> Self {
info.isPersisted = true
requirePersist = true
return self
}

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

/// Custom tag to mark the job
public func addTag(tag: String) -> Self {
assertNotEmptyString(tag)
info.tags.insert(tag)
if let constraint: TagConstraint = getConstraint(info.constraints) {
constraint.insert(tag: tag)
return self
}

var set = Set<String>()
set.insert(tag)

info.constraints.append(TagConstraint(tags: set))
return self
}

Expand All @@ -142,15 +145,15 @@ public final class JobBuilder {
return self
}

/// Call if the job can only run when the device is charging
/// Call if job can only run when the device is charging
public func requireCharging() -> Self {
info.requireCharging = true
info.constraints.append(BatteryChargingConstraint())
return self
}

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

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

/// Add job to the JobQueue
public func schedule(manager: SwiftQueueManager) {
if info.isPersisted {
// Check if we will be able to serialize args
if requirePersist {
let constraint: UniqueUUIDConstraint? = getConstraint(info)
if constraint == nil {
info.constraints.append(UniqueUUIDConstraint(uuid: UUID().uuidString, override: false, includeExecutingJob: false))
}
assert(JSONSerialization.isValidJSONObject(info.params))
info.constraints.append(PersisterConstraint(serializer: manager.params.serializer, persister: manager.params.persister))
}

manager.enqueue(info: info)
}

}
Loading

0 comments on commit e0e80c8

Please sign in to comment.