lucas34 / SwiftQueue

Job Scheduler for IOS with Concurrent run, failure/retry, persistence, repeat, delay and more
MIT License
409 stars 42 forks source link

Only one job are called #194

Closed jborges05 closed 5 years ago

jborges05 commented 5 years ago

Hello, i'm using SwiftQueue to createa upload/download queue in my app. When i try to run the queue return allways the same image for all objects in que

Can you help me? I have arround 500 images to upload but queue only run the same job for te 500 images. What i'm doing wrong, im using the last version of pod. `import Foundation import SwiftQueue import Crashlytics import Alamofire

class MIImagesInQueueDownload: NSObject, NSCoding { var url: String?

init(url: String?) {
    self.url = url
}

func encode(with aCoder: NSCoder) {
    aCoder.encode(url, forKey: "url")
}

required init(coder aDecoder: NSCoder) {
    if let url = aDecoder.decodeObject(forKey: "url") as? String {
        self.url = url
    }
}

static func ==(lhs: MIImagesInQueueDownload, rhs: MIImagesInQueueDownload) -> Bool {
    return lhs.url ?? "" == rhs.url ?? ""
}

}

class MIImagesInQueueUpload: NSObject, NSCoding { var filePath: String = "" var fileName: String = "" var assignmentUUID: String = "" var url:String = ""

init(filePath: String, fileName: String, assignmentUUID: String, url:String) {
    self.fileName = fileName
    self.filePath = filePath
    self.assignmentUUID = assignmentUUID
    self.url = url
}

func encode(with aCoder: NSCoder) {
    aCoder.encode(filePath, forKey: "filePath")
    aCoder.encode(fileName, forKey: "fileName")
    aCoder.encode(assignmentUUID, forKey: "assignmentUUID")
    aCoder.encode(url, forKey: "url")
}

required init(coder aDecoder: NSCoder) {
    if let filename = aDecoder.decodeObject(forKey: "fileName") as? String {
        self.fileName = filename
    }

    if let filePath = aDecoder.decodeObject(forKey: "filePath") as? String {
        self.filePath = filePath
    }

    if let uuid = aDecoder.decodeObject(forKey: "assignmentUUID") as? String {
        self.assignmentUUID = uuid
    }

    if let url = aDecoder.decodeObject(forKey: "url") as? String {
        self.url = url
    }
}

static func ==(lhs: MIImagesInQueueUpload, rhs: MIImagesInQueueUpload) -> Bool {
    return lhs.fileName == rhs.fileName && lhs.assignmentUUID == rhs.assignmentUUID && lhs.filePath == rhs.filePath && lhs.url == rhs.url
}

}

public struct ImageError: Error { let msg: String }

public struct UploadError: Error { let msg: String }

extension ImageError: LocalizedError { public var errorDescription: String? { return NSLocalizedString(msg, comment: "") } }

extension UploadError: LocalizedError { public var errorDescription: String? { return NSLocalizedString(msg, comment: "") } }

class downloadOperation: Job { let image: MIImagesInQueueDownload static let type = "downloadImage"

required init(image: MIImagesInQueueDownload) {
    self.image = image
}

func onRun(callback: JobResult) {
    self.downloadImage(with: self.image) { (error) in
        if let error = error {
            callback.done(.fail(error))
        } else {
            callback.done(.success)
        }
    }
}

func onRetry(error: Error) -> RetryConstraint {
    if error is UploadError {
        return RetryConstraint.retry(delay: 3600)
    } else {
        return RetryConstraint.cancel
    }
}

func onRemove(result: JobCompletion) {
    // This job will never run anymore
    switch result {
    case .success:
        // Job success
        break

    case .fail(let error):
        Logger.shared.log(message: error.localizedDescription, vc: "ImageDownload JOB")
        break

    }
}

private func downloadImage(with image:MIImagesInQueueDownload, onComplete:@escaping onCompleteupload) {
    guard let urlImage = image.url?.encodeURL() else { return }
    MIImageQueue.shared.imageDownloader.downloadImage(with: urlImage, options: [], progressBlock: nil) { (image, error, url, data) in
        Logger.shared.log(message: error?.localizedDescription ?? "", vc: "imageDowload")
        guard let image = image else {
            onComplete(ImageError(msg: "missing file at server"))
            return
        }
        guard let url = url else {
            onComplete(ImageError(msg: "missing file at server"))
            return
        }
        MIHelper.shared.storeImage(image: image, fileURL: url.absoluteString, fileName: url.lastPathComponent, execute: { (imageDevice) in
            onComplete(nil)
        })
    }
}

}

class uploadOperation: Job { let image: MIImagesInQueueUpload lazy var uploadQueue: DispatchQueue = { var queue = DispatchQueue(label: "com.uscope.photoId.ImagesUpload", qos: DispatchQoS.background, attributes: .concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, target: DispatchQueue.global(qos: .background)) return queue }()

static let type = "uploadImage"

required init(image: MIImagesInQueueUpload) {
    self.image = image
    print(image.filePath)
}

func onRun(callback: JobResult) {
    print(image.fileName)
    self.uploadImage(with: self.image) { (error) in
        if let error = error {
            callback.done(.fail(error))
        } else {
            callback.done(.success)
        }
    }
}

func onRetry(error: Error) -> RetryConstraint {
    if error is UploadError || error is ImageError {
        return RetryConstraint.retry(delay: 3600)
    } else {
        return RetryConstraint.cancel
    }
}

func onRemove(result: JobCompletion) {
    // This job will never run anymore
    switch result {
    case .success:
        // Job success
        break

    case .fail(let error):
        Logger.shared.log(message: error.localizedDescription, vc: "ImageUpload JOB")
        break

    }
}

fileprivate func uploadImage(with imageQueue:MIImagesInQueueUpload, onComplete:@escaping onCompleteupload){
    let object = imageQueue
    if let filePathURL = URL(string: imageQueue.filePath), let profileIdToSet = imageQueue.url.getProfileID() {
        let filePath = filePathURL.imagesFolderPath()
        if filePath.extists() {
            let parameters:Parameters = ["filename": object.fileName, "sourceFileUri": filePath.absoluteString, "assignmentId": object.assignmentUUID, "profileId": profileIdToSet]
            let parametersLog:[String:String] = ["filename": object.fileName, "assignmentId": object.assignmentUUID, "profileId": profileIdToSet]
            print(parameters)
            Answers.logCustomEvent(withName: "image upload", customAttributes: parametersLog)
            MIConnnection.shared.alamofireManager.upload(multipartFormData: { (multipartFormData) in
                multipartFormData.append(filePath, withName: "bill", fileName: object.fileName.isEmpty ? filePath.getImageName() : object.fileName, mimeType: "image/jpeg")
            }, with: APINetworkRouter.upload(parameters: parameters), encodingCompletion: { [weak self] (result) in
                guard let strongSelf = self else { return }
                switch result {
                case .success(let upload, _, _):
                    upload.uploadProgress(closure: { (progress) in
                        Logger.shared.log(message: "Upload progress \(progress.fractionCompleted)", vc: String(describing: self))
                    })
                    upload.customValidation().responseJSON(queue: strongSelf.uploadQueue, completionHandler: { (response) in
                        switch response.result {
                        case .success(let responseJSON):
                            guard let responseObject = responseJSON as? DefaultDict else { return }
                            guard let success = responseObject["success"]?.boolValue else { return }
                            guard let message = responseObject["message"] as? String else { return }
                            if success {
                                Answers.logCustomEvent(withName: "image upload success", customAttributes: ["success" : success, "message": message])
                                let objectNotification = ["url": imageQueue.url.getImageNameWhitoutURL(), "assignmnentId" : object.assignmentUUID]
                                NotificationCenter.default.post(name: Notifications.didChangeUpload, object: nil, userInfo:objectNotification)
                                DispatchQueue.main.async {
                                    onComplete(nil)
                                }
                            } else {
                                DispatchQueue.main.async {
                                    onComplete(UploadError(msg: message))
                                }
                                Answers.logCustomEvent(withName: "image upload fail", customAttributes: ["success" : success, "message": message])
                                return
                            }
                        case .failure(let error):
                            if isDebug {
                                error.manageAlamofireError()
                            }
                            DispatchQueue.main.async {
                                onComplete(UploadError(msg: error.localizedDescription))
                            }
                            return
                        }
                    })
                case .failure(let error):
                    if isDebug {
                        error.manageAlamofireError()
                    }
                    onComplete(error)
                    return
                }
            })
        } else {
            DispatchQueue.main.async {
                onComplete(ImageError(msg: "missing file at disk"))
            }
            Answers.logCustomEvent(withName: "image upload fail", customAttributes: ["error" : "missing file at disk"])
        }
    }
}

}

class UploaderDownloaderManager { static let shared:UploaderDownloaderManager = UploaderDownloaderManager() let queueManager = SwiftQueueManagerBuilder(creator: mainJobCreator()).set(logger: ConsoleLogger()).build()

func uploadImage(with imageQueue:MIImagesInQueueUpload) {
    if let user = MIFirebaseDatabaseManager.shared.userApp {
        JobBuilder(type: uploadOperation.type)
            // Prevent adding the same job multiple times
            .singleInstance(forId: imageQueue.url)
            // Different group name will run in parallel
            // But one by one for a group
            .group(name: "uploadPicture")
            // Sending tweet will require internet. At least cellular.
            // Will also be executed if connected to wifi.
            .internet(atLeast: user.cellarSync ? .any : .wifi)
            // Content of your tweet. Can be a class, struct or anything
            .with(params: ["fileURI" : imageQueue.url, "filePath" : imageQueue.filePath, "fileName" : imageQueue.fileName, "assignmentId" : imageQueue.assignmentUUID, "profileId": user.userID])
            // unlimited retry by default
            .retry(limit: .limited(10))
            // set persist by default
            .persist(required: true)
            // set periodic by default
            //                .periodic()
            // Cancel the job if it's not completed after one week
            .deadline(date: Date(timeIntervalSinceNow: 604_800))
            // Insert to queue
            .schedule(manager: self.queueManager)
    }
}

func downloadImage(with imageQueue:MIImagesInQueueDownload) {
    if let user = MIFirebaseDatabaseManager.shared.userApp {
        JobBuilder(type: downloadOperation.type)
            // Prevent adding the same job multiple times
            .singleInstance(forId: imageQueue.url ?? "")
            // Different group name will run in parallel
            // But one by one for a group
            .group(name: "downloadPicture")
            // Sending tweet will require internet. At least cellular.
            // Will also be executed if connected to wifi.
            .internet(atLeast: user.cellarSync ? .any : .wifi)
            // Content of your tweet. Can be a class, struct or anything
            .with(params: ["fileURI" : imageQueue.url ?? ""])
            // unlimited retry by default
            .retry(limit: .limited(5))
            // set persist by default
            .persist(required: true)
            // set periodic by default
            .periodic()
            // Cancel the job if it's not completed after 24 hours
            .deadline(date: Date(timeIntervalSinceNow: 86_400))
            // Insert to queue
            .schedule(manager: self.queueManager)
    }
}

func downloadFromURL(with url:URL) {
    if let user = MIFirebaseDatabaseManager.shared.userApp {
        JobBuilder(type: downloadOperation.type)
            // Prevent adding the same job multiple times
            .singleInstance(forId: url.absoluteString)
            // Different group name will run in parallel
            // But one by one for a group
            .group(name: "downloadPicture")
            // Sending tweet will require internet. At least cellular.
            // Will also be executed if connected to wifi.
            .internet(atLeast: user.cellarSync ? .any : .wifi)
            // Content of your tweet. Can be a class, struct or anything
            .with(params: ["fileURI" : url.absoluteString])
            // unlimited retry by default
            .retry(limit: .limited(5))
            // set persist by default
            .persist(required: true)
            // set periodic by default
            .periodic()
            // Cancel the job if it's not completed after 24 hours
            .deadline(date: Date(timeIntervalSinceNow: 86_400))
            // Insert to queue
            .schedule(manager: self.queueManager)
    }
}

}

class mainJobCreator: JobCreator { func create(type: String, params: [String : Any]?) -> Job { // check for job and params type if type == downloadOperation.type { return downloadOperation(image: MIImagesInQueueDownload(url: params?["fileURI"] as? String ?? "")) } else if type == uploadOperation.type { return uploadOperation(image: MIImagesInQueueUpload(filePath: params?["filePath"] as? String ?? "", fileName: params?["fileName"] as? String ?? "", assignmentUUID: params?["assignmentId"] as? String ?? "", url: params?["fileURI"] as? String ?? "")) } else { fatalError("No Job !") } } }

`

lucas34 commented 5 years ago

I went briefly through the code and I don't really see any major problem in the implementation. What do you mean by always run the same job ? The JobCreator in your case seems to return a new instance for each job. Are you referring to the parameter that is always the same ?

kuyu12 commented 5 years ago

Hey, I try to run similar code in my computer and seem that everything is working well. my question is as lucas34 asked is:

lucas34 commented 5 years ago

Closing the issue since It seems to not be a library issue.