From 6f779a54076a9df401b1c544d57edc93e33f0e06 Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Thu, 18 Feb 2016 17:09:30 +0100 Subject: [PATCH 01/14] =?UTF-8?q?=F0=9F=8D=8B=20sanitise=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EDQueue.podspec | 6 +- EDQueue/EDQueue.h | 13 +++- EDQueue/EDQueue.m | 82 +++++++++++++++++++++---- EDQueue/EDQueueStorageEngine.h | 2 +- EDQueue/EDQueueStorageEngine.m | 65 ++++++++++++++++++++ EDQueue/EDQueueStorageJob.h | 32 ++++++++++ EDQueue/EDQueueStorageJob.m | 45 ++++++++++++++ Project/queue.xcodeproj/project.pbxproj | 6 ++ 8 files changed, 233 insertions(+), 18 deletions(-) create mode 100644 EDQueue/EDQueueStorageJob.h create mode 100644 EDQueue/EDQueueStorageJob.m diff --git a/EDQueue.podspec b/EDQueue.podspec index 2618403..8aab26d 100644 --- a/EDQueue.podspec +++ b/EDQueue.podspec @@ -1,14 +1,14 @@ Pod::Spec.new do |s| s.name = 'EDQueue' - s.version = '0.7.1' + s.version = '0.7.2' s.license = 'MIT' s.summary = 'A persistent background job queue for iOS.' s.homepage = 'https://github.com/thisandagain/queue' - s.authors = {'Andrew Sliwinski' => 'andrewsliwinski@acm.org', 'Francois Lambert' => 'flambert@mirego.com'} + s.authors = {'Andrew Sliwinski' => 'andrewsliwinski@acm.org', 'Francois Lambert' => 'flambert@mirego.com', 'Oleg Shanyuk' => 'oleg.shanyuk@gmail.com'} s.source = { :git => 'https://github.com/thisandagain/queue.git', :tag => 'v0.7.1' } s.platform = :ios, '5.0' s.source_files = 'EDQueue' s.library = 'sqlite3.0' s.requires_arc = true - s.dependency 'FMDB', '~> 2.0' + s.dependency 'FMDB', '~> 2.1' end diff --git a/EDQueue/EDQueue.h b/EDQueue/EDQueue.h index 9d49848..bd1d9fb 100755 --- a/EDQueue/EDQueue.h +++ b/EDQueue/EDQueue.h @@ -6,7 +6,9 @@ // Copyright (c) 2012 Andrew Sliwinski. All rights reserved. // -#import +#import + +NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, EDQueueResult) { EDQueueResultSuccess = 0, @@ -22,7 +24,12 @@ extern NSString *const EDQueueJobDidSucceed; extern NSString *const EDQueueJobDidFail; extern NSString *const EDQueueDidDrain; +extern NSString *const EDQueueNameKey; +extern NSString *const EDQueueDataKey; + + @protocol EDQueueDelegate; + @interface EDQueue : NSObject + (EDQueue *)sharedInstance; @@ -33,7 +40,7 @@ extern NSString *const EDQueueDidDrain; @property (nonatomic, readonly) BOOL isActive; @property (nonatomic) NSUInteger retryLimit; -- (void)enqueueWithData:(id)data forTask:(NSString *)task; +- (void)enqueueWithData:(nullable NSDictionary *)data forTask:(NSString *)task; - (void)start; - (void)stop; - (void)empty; @@ -49,3 +56,5 @@ extern NSString *const EDQueueDidDrain; - (EDQueueResult)queue:(EDQueue *)queue processJob:(NSDictionary *)job; - (void)queue:(EDQueue *)queue processJob:(NSDictionary *)job completion:(EDQueueCompletionBlock)block; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index a26f870..e009c1b 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -15,6 +15,11 @@ NSString *const EDQueueJobDidFail = @"EDQueueJobDidFail"; NSString *const EDQueueDidDrain = @"EDQueueDidDrain"; +NSString *const EDQueueNameKey = @"name"; +NSString *const EDQueueDataKey = @"data"; + +NS_ASSUME_NONNULL_BEGIN + @interface EDQueue () { BOOL _isRunning; @@ -23,7 +28,7 @@ @interface EDQueue () } @property (nonatomic) EDQueueStorageEngine *engine; -@property (nonatomic, readwrite) NSString *activeTask; +@property (nonatomic, readwrite, nullable) NSString *activeTask; @end @@ -75,7 +80,7 @@ - (void)dealloc * * @return {void} */ -- (void)enqueueWithData:(id)data forTask:(NSString *)task +- (void)enqueueWithData:(nullable NSDictionary *)data forTask:(NSString *)task { if (data == nil) data = @{}; [self.engine createJob:data forTask:task]; @@ -131,7 +136,11 @@ - (void)start if (!self.isRunning) { _isRunning = YES; [self tick]; - [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueDidStart, @"name", nil, @"data", nil] waitUntilDone:false]; + + NSDictionary *object = @{ EDQueueNameKey : EDQueueDidStart }; + +// [self performSelectorOnMainThread:@selector(postNotificationOnMainThread:) withObject:object waitUntilDone:NO]; + [self postNotificationOnMainThread:object]; } } @@ -145,7 +154,11 @@ - (void)stop { if (self.isRunning) { _isRunning = NO; - [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueDidStop, @"name", nil, @"data", nil] waitUntilDone:false]; + + NSDictionary *object = @{ EDQueueNameKey : EDQueueDidStop }; + +// [self performSelectorOnMainThread:@selector(postNotification:) withObject:object waitUntilDone:NO]; + [self postNotificationOnMainThread:object]; } } @@ -177,8 +190,8 @@ - (void)tick if (self.isRunning && !self.isActive && [self.engine fetchJobCount] > 0) { // Start job _isActive = YES; - id job = [self.engine fetchJob]; - self.activeTask = [(NSDictionary *)job objectForKey:@"task"]; + NSDictionary *job = [self.engine fetchJob]; + self.activeTask = [job objectForKey:@"task"]; // Pass job to delegate if ([self.delegate respondsToSelector:@selector(queue:processJob:completion:)]) { @@ -197,15 +210,44 @@ - (void)tick - (void)processJob:(NSDictionary*)job withResult:(EDQueueResult)result { + if (!job) { + job = @{}; + } // Check result switch (result) { case EDQueueResultSuccess: - [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueJobDidSucceed, @"name", job, @"data", nil] waitUntilDone:false]; + +// NSDictionary *object = @{ +// EDQueueNameKey : EDQueueJobDidSucceed, +// EDQueueDataKey : job +// }; + //[NSDictionary dictionaryWithObjectsAndKeys:EDQueueJobDidSucceed, EDQueueNameKey, job, EDQueueDataKey, nil] + +// [self performSelectorOnMainThread:@selector(postNotification:) +// withObject:@{ +// EDQueueNameKey : EDQueueJobDidSucceed, +// EDQueueDataKey : job +// } +// waitUntilDone:false]; + + [self postNotificationOnMainThread:@{ + EDQueueNameKey : EDQueueJobDidSucceed, + EDQueueDataKey : job + }]; + [self.engine removeJob:[job objectForKey:@"id"]]; break; + case EDQueueResultFail: - [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueJobDidFail, @"name", job, @"data", nil] waitUntilDone:true]; +// [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueJobDidFail, EDQueueNameKey, job, EDQueueDataKey, nil] waitUntilDone:true]; + + [self postNotificationOnMainThread:@{ + EDQueueNameKey : EDQueueJobDidFail, + EDQueueDataKey : job + }]; + NSUInteger currentAttempt = [[job objectForKey:@"attempts"] intValue] + 1; + if (currentAttempt < self.retryLimit) { [self.engine incrementAttemptForJob:[job objectForKey:@"id"]]; } else { @@ -213,7 +255,14 @@ - (void)processJob:(NSDictionary*)job withResult:(EDQueueResult)result } break; case EDQueueResultCritical: - [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueJobDidFail, @"name", job, @"data", nil] waitUntilDone:false]; + +// [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueJobDidFail, EDQueueNameKey, job, EDQueueDataKey, nil] waitUntilDone:false]; + + [self postNotificationOnMainThread:@{ + EDQueueNameKey : EDQueueJobDidFail, + EDQueueDataKey : job + }]; + [self errorWithMessage:@"Critical error. Job canceled."]; [self.engine removeJob:[job objectForKey:@"id"]]; break; @@ -224,7 +273,10 @@ - (void)processJob:(NSDictionary*)job withResult:(EDQueueResult)result // Drain if ([self.engine fetchJobCount] == 0) { - [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueDidDrain, @"name", nil, @"data", nil] waitUntilDone:false]; +// [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueDidDrain, EDQueueNameKey, nil, EDQueueDataKey, nil] waitUntilDone:false]; + [self postNotificationOnMainThread:@{ + EDQueueNameKey : EDQueueDidDrain, + }]; } else { [self performSelectorOnMainThread:@selector(tick) withObject:nil waitUntilDone:false]; } @@ -239,9 +291,13 @@ - (void)processJob:(NSDictionary*)job withResult:(EDQueueResult)result * * @return {void} */ -- (void)postNotification:(NSDictionary *)object +- (void)postNotificationOnMainThread:(NSDictionary *)object { - [[NSNotificationCenter defaultCenter] postNotificationName:[object objectForKey:@"name"] object:[object objectForKey:@"data"]]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:[object objectForKey:EDQueueNameKey] + object:[object objectForKey:EDQueueDataKey]]; + }); + } /** @@ -257,3 +313,5 @@ - (void)errorWithMessage:(NSString *)message } @end + +NS_ASSUME_NONNULL_END diff --git a/EDQueue/EDQueueStorageEngine.h b/EDQueue/EDQueueStorageEngine.h index aec98f4..6f84e69 100755 --- a/EDQueue/EDQueueStorageEngine.h +++ b/EDQueue/EDQueueStorageEngine.h @@ -13,7 +13,7 @@ @property (retain) FMDatabaseQueue *queue; -- (void)createJob:(id)data forTask:(id)task; +- (void)createJob:(NSDictionary *)data forTask:(id)task; - (BOOL)jobExistsForTask:(id)task; - (void)incrementAttemptForJob:(NSNumber *)jid; - (void)removeJob:(NSNumber *)jid; diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index 9d2e907..c67035e 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -13,6 +13,67 @@ #import "FMDatabasePool.h" #import "FMDatabaseQueue.h" +#import "EDQueueStorageJob.h" + + +//static NSString *sql_createTableIfNotExists() +//{ +// return [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS queue (%@ INTEGER PRIMARY KEY, %@ TEXT NOT NULL, %@ TEXT NOT NULL, %@ INTEGER DEFAULT 0, %@ STRING DEFAULT (strftime('%%s','now')) NOT NULL, udef_1 TEXT, udef_2 TEXT)",EDQueueStorageJobIdKey, EDQueueStorageJobTaskKey, EDQueueStorageJobDataKey, EDQueueStorageJobAttemptsKey, EDQueueStorageJobStampKey]; +//} +// +//static NSString *sql_createJob() +//{ +// return [NSString stringWithFormat:@"INSERT INTO queue (%@, %@) VALUES (?, ?)", EDQueueStorageJobTaskKey, EDQueueStorageJobDataKey ]; +//} +// +//static NSString *sql_deleteJob() +//{ +// return [NSString stringWithFormat:@"DELETE FROM queue WHERE %@ = ?", EDQueueStorageJobIdKey]; +//} +// +//static NSString *sql_jobExistsForTask; +//static NSString *sql_incrementAttemptForJob; +//static NSString *sql_fetchJobCount; +//static NSString *sql_fetchJob; +//static NSString *sql_fetchJobForTask; +//static NSString *sql_deleteAllJobs; + +@interface EDQueueStorageJob(FMResultSet) + +-(instancetype)initWithFMResultSet:(FMResultSet *)resutSet; + +@end + +@implementation EDQueueStorageJob(FMResultSet) + +- (instancetype)initWithFMResultSet:(FMResultSet *)rs +{ + NSString *json = [rs stringForColumn:@"data"]; + + NSString *data = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingMutableContainers + error:nil]; + + NSDictionary *job = @{ + EDQueueStorageJobIdKey : [NSNumber numberWithInt:[rs intForColumn:@"id"]], + EDQueueStorageJobTaskKey : [rs stringForColumn:@"task"], + EDQueueStorageJobTaskKey : data, + EDQueueStorageJobAttemptsKey: [NSNumber numberWithInt:[rs intForColumn:@"attempts"]], + EDQueueStorageJobStampKey: [rs stringForColumn:@"stamp"] + }; + + return [self initWithDictionary:job]; +} + +@end + + + +@interface EDQueueStorageEngine() + + +@end + @implementation EDQueueStorageEngine #pragma mark - Init @@ -28,6 +89,10 @@ - (id)init // Allocate the queue _queue = [[FMDatabaseQueue alloc] initWithPath:path]; + + +// NSString *sql = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS queue (%@ INTEGER PRIMARY KEY, %@ TEXT NOT NULL, %@ TEXT NOT NULL, %@ INTEGER DEFAULT 0, %@ STRING DEFAULT (strftime('%%s','now')) NOT NULL, udef_1 TEXT, udef_2 TEXT)",EDQueueStorageJobIdKey, EDQueueStorageJobTaskKey, EDQueueStorageJobDataKey, EDQueueStorageJobAttemptsKey, EDQueueStorageJobStampKey]; + [self.queue inDatabase:^(FMDatabase *db) { [db executeUpdate:@"CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY, task TEXT NOT NULL, data TEXT NOT NULL, attempts INTEGER DEFAULT 0, stamp STRING DEFAULT (strftime('%s','now')) NOT NULL, udef_1 TEXT, udef_2 TEXT)"]; [self _databaseHadError:[db hadError] fromDatabase:db]; diff --git a/EDQueue/EDQueueStorageJob.h b/EDQueue/EDQueueStorageJob.h new file mode 100644 index 0000000..d2776f6 --- /dev/null +++ b/EDQueue/EDQueueStorageJob.h @@ -0,0 +1,32 @@ +// +// EDQueueJob.h +// queue +// +// Created by Oleg Shanyuk on 18/02/16. +// Copyright © 2016 DIY, Co. All rights reserved. +// + +#import + + +extern NSString *const EDQueueStorageJobIdKey; +extern NSString *const EDQueueStorageJobTaskKey; +extern NSString *const EDQueueStorageJobDataKey; +extern NSString *const EDQueueStorageJobAttemptsKey; +extern NSString *const EDQueueStorageJobStampKey; + +@interface EDQueueStorageJob : NSObject + +@property (nonatomic, readonly) NSNumber *jobId; +@property (nonatomic, readonly) NSString *task; +@property (nonatomic, readonly) NSData *data; +@property (nonatomic, readonly) NSNumber* attempts; +@property (nonatomic, readonly) NSString *stamp; +@property (nonatomic, readonly) NSDictionary *dictionaryRepresentation; + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary; +- (instancetype)init NS_UNAVAILABLE; + +- (NSDictionary*)dictionaryRepresentation; + +@end diff --git a/EDQueue/EDQueueStorageJob.m b/EDQueue/EDQueueStorageJob.m new file mode 100644 index 0000000..57c642c --- /dev/null +++ b/EDQueue/EDQueueStorageJob.m @@ -0,0 +1,45 @@ +// +// EDQueueJob.m +// queue +// +// Created by Oleg Shanyuk on 18/02/16. +// Copyright © 2016 DIY, Co. All rights reserved. +// + +#import "EDQueueStorageJob.h" + +NSString *const EDQueueStorageJobIdKey = @"id"; +NSString *const EDQueueStorageJobTaskKey = @"task"; +NSString *const EDQueueStorageJobDataKey = @"data"; +NSString *const EDQueueStorageJobAttemptsKey = @"atempts"; +NSString *const EDQueueStorageJobStampKey = @"stamp"; + +@implementation EDQueueStorageJob + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + self = [super init]; + + if (self) { + _jobId = [dictionary[EDQueueStorageJobIdKey] copy]; + _task = [dictionary[EDQueueStorageJobTaskKey] copy]; + _data = [dictionary[EDQueueStorageJobDataKey] copy]; + _attempts = [dictionary[EDQueueStorageJobAttemptsKey] copy]; + _stamp = [dictionary[EDQueueStorageJobStampKey] copy]; + } + + return self; +} + +- (NSDictionary *)dictionaryRepresentation +{ + return @{ + EDQueueStorageJobIdKey : _jobId, + EDQueueStorageJobTaskKey : _task, + EDQueueStorageJobDataKey : _data, + EDQueueStorageJobAttemptsKey : _attempts, + EDQueueStorageJobStampKey : _stamp + }; +} + +@end diff --git a/Project/queue.xcodeproj/project.pbxproj b/Project/queue.xcodeproj/project.pbxproj index 90332f1..65ba684 100644 --- a/Project/queue.xcodeproj/project.pbxproj +++ b/Project/queue.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 7CA53AF41C75F88E00420814 /* EDQueueStorageJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CA53AF31C75F88E00420814 /* EDQueueStorageJob.m */; }; C32F6E07160795C3004BA8A1 /* EDQueue.podspec in Resources */ = {isa = PBXBuildFile; fileRef = C32F6E06160795C3004BA8A1 /* EDQueue.podspec */; }; C32F6E0C16079680004BA8A1 /* libsqlite3.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C32F6E0B16079680004BA8A1 /* libsqlite3.0.dylib */; }; C32F6E191607AC35004BA8A1 /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = C32F6E101607AC35004BA8A1 /* FMDatabase.m */; }; @@ -29,6 +30,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 7CA53AF21C75F88E00420814 /* EDQueueStorageJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EDQueueStorageJob.h; path = ../EDQueue/EDQueueStorageJob.h; sourceTree = ""; }; + 7CA53AF31C75F88E00420814 /* EDQueueStorageJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EDQueueStorageJob.m; path = ../EDQueue/EDQueueStorageJob.m; sourceTree = ""; }; C32F6E06160795C3004BA8A1 /* EDQueue.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = EDQueue.podspec; path = ../EDQueue.podspec; sourceTree = ""; }; C32F6E0B16079680004BA8A1 /* libsqlite3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.0.dylib; path = usr/lib/libsqlite3.0.dylib; sourceTree = SDKROOT; }; C32F6E0F1607AC35004BA8A1 /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = ""; }; @@ -84,6 +87,8 @@ C395D533159E57880041510C /* EDQueue.m */, C32F6E221607E1FB004BA8A1 /* EDQueueStorageEngine.h */, C32F6E231607E1FB004BA8A1 /* EDQueueStorageEngine.m */, + 7CA53AF21C75F88E00420814 /* EDQueueStorageJob.h */, + 7CA53AF31C75F88E00420814 /* EDQueueStorageJob.m */, ); name = EDQueue; sourceTree = ""; @@ -238,6 +243,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7CA53AF41C75F88E00420814 /* EDQueueStorageJob.m in Sources */, C395D519159E56760041510C /* main.m in Sources */, C395D51D159E56760041510C /* EDAppDelegate.m in Sources */, C395D520159E56760041510C /* EDViewController.m in Sources */, From 9d08d348ad3ecadc497e272d9c039dc28ec1b85d Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Fri, 19 Feb 2016 12:50:08 +0100 Subject: [PATCH 02/14] upgraded project to the more-less modern state (iOS 7 bottomline) added nullability added EDQueueJob instead of dictionary + keys (for user data it still provides userInfo dictionary, nullable) --- EDQueue.podspec | 6 +- EDQueue/EDQueue.h | 10 +- EDQueue/EDQueue.m | 60 ++++------- EDQueue/EDQueueJob.h | 35 +++++++ EDQueue/EDQueueJob.m | 42 ++++++++ EDQueue/EDQueueStorageEngine.h | 25 +++-- EDQueue/EDQueueStorageEngine.m | 128 ++++++++++-------------- EDQueue/EDQueueStorageJob.h | 32 ------ EDQueue/EDQueueStorageJob.m | 45 --------- Project/Default-568h@2x.png | Bin 0 -> 18594 bytes Project/queue.xcodeproj/project.pbxproj | 50 +++++++-- Project/queue/EDAppDelegate.m | 27 +---- Project/queue/EDViewController.m | 9 +- Project/queue/queue-Info.plist | 2 +- 14 files changed, 223 insertions(+), 248 deletions(-) create mode 100644 EDQueue/EDQueueJob.h create mode 100644 EDQueue/EDQueueJob.m delete mode 100644 EDQueue/EDQueueStorageJob.h delete mode 100644 EDQueue/EDQueueStorageJob.m create mode 100644 Project/Default-568h@2x.png diff --git a/EDQueue.podspec b/EDQueue.podspec index 8aab26d..5024c1b 100644 --- a/EDQueue.podspec +++ b/EDQueue.podspec @@ -3,10 +3,10 @@ Pod::Spec.new do |s| s.version = '0.7.2' s.license = 'MIT' s.summary = 'A persistent background job queue for iOS.' - s.homepage = 'https://github.com/thisandagain/queue' + s.homepage = 'https://github.com/gelosi/queue' s.authors = {'Andrew Sliwinski' => 'andrewsliwinski@acm.org', 'Francois Lambert' => 'flambert@mirego.com', 'Oleg Shanyuk' => 'oleg.shanyuk@gmail.com'} - s.source = { :git => 'https://github.com/thisandagain/queue.git', :tag => 'v0.7.1' } - s.platform = :ios, '5.0' + s.source = { :git => 'https://github.com/gelosi/queue.git', :tag => 'v0.7.2' } + s.platform = :ios, '7.0' s.source_files = 'EDQueue' s.library = 'sqlite3.0' s.requires_arc = true diff --git a/EDQueue/EDQueue.h b/EDQueue/EDQueue.h index bd1d9fb..30183c2 100755 --- a/EDQueue/EDQueue.h +++ b/EDQueue/EDQueue.h @@ -8,6 +8,8 @@ #import +#import "EDQueueJob.h" + NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, EDQueueResult) { @@ -40,21 +42,19 @@ extern NSString *const EDQueueDataKey; @property (nonatomic, readonly) BOOL isActive; @property (nonatomic) NSUInteger retryLimit; -- (void)enqueueWithData:(nullable NSDictionary *)data forTask:(NSString *)task; +- (void)enqueueJob:(EDQueueJob *)job; - (void)start; - (void)stop; - (void)empty; - (BOOL)jobExistsForTask:(NSString *)task; - (BOOL)jobIsActiveForTask:(NSString *)task; -- (NSDictionary *)nextJobForTask:(NSString *)task; +- (nullable EDQueueJob *)nextJobForTask:(NSString *)task; @end @protocol EDQueueDelegate -@optional -- (EDQueueResult)queue:(EDQueue *)queue processJob:(NSDictionary *)job; -- (void)queue:(EDQueue *)queue processJob:(NSDictionary *)job completion:(EDQueueCompletionBlock)block; +- (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(EDQueueCompletionBlock)block; @end NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index e009c1b..557c782 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -18,6 +18,14 @@ NSString *const EDQueueNameKey = @"name"; NSString *const EDQueueDataKey = @"data"; + +NSString *const EDQueueStorageJobIdKey = @"id"; +NSString *const EDQueueStorageJobTaskKey = @"task"; +NSString *const EDQueueStorageJobDataKey = @"data"; +NSString *const EDQueueStorageJobAttemptsKey = @"atempts"; +NSString *const EDQueueStorageJobStampKey = @"stamp"; + + NS_ASSUME_NONNULL_BEGIN @interface EDQueue () @@ -80,10 +88,9 @@ - (void)dealloc * * @return {void} */ -- (void)enqueueWithData:(nullable NSDictionary *)data forTask:(NSString *)task +- (void)enqueueJob:(EDQueueJob *)job { - if (data == nil) data = @{}; - [self.engine createJob:data forTask:task]; + [self.engine createJob:job]; [self tick]; } @@ -120,9 +127,9 @@ - (BOOL)jobIsActiveForTask:(NSString *)task * * @return {NSArray} */ -- (NSDictionary *)nextJobForTask:(NSString *)task +- (nullable EDQueueJob *)nextJobForTask:(NSString *)task { - NSDictionary *nextJobForTask = [self.engine fetchJobForTask:task]; + EDQueueJob *nextJobForTask = [self.engine fetchJobForTask:task]; return nextJobForTask; } @@ -190,81 +197,56 @@ - (void)tick if (self.isRunning && !self.isActive && [self.engine fetchJobCount] > 0) { // Start job _isActive = YES; - NSDictionary *job = [self.engine fetchJob]; - self.activeTask = [job objectForKey:@"task"]; + EDQueueJob *job = [self.engine fetchJob]; + self.activeTask = job.task; // Pass job to delegate - if ([self.delegate respondsToSelector:@selector(queue:processJob:completion:)]) { [self.delegate queue:self processJob:job completion:^(EDQueueResult result) { [self processJob:job withResult:result]; self.activeTask = nil; }]; - } else { - EDQueueResult result = [self.delegate queue:self processJob:job]; - [self processJob:job withResult:result]; - self.activeTask = nil; - } } }); } -- (void)processJob:(NSDictionary*)job withResult:(EDQueueResult)result +- (void)processJob:(EDQueueJob*)job withResult:(EDQueueResult)result { - if (!job) { - job = @{}; - } // Check result switch (result) { case EDQueueResultSuccess: -// NSDictionary *object = @{ -// EDQueueNameKey : EDQueueJobDidSucceed, -// EDQueueDataKey : job -// }; - //[NSDictionary dictionaryWithObjectsAndKeys:EDQueueJobDidSucceed, EDQueueNameKey, job, EDQueueDataKey, nil] - -// [self performSelectorOnMainThread:@selector(postNotification:) -// withObject:@{ -// EDQueueNameKey : EDQueueJobDidSucceed, -// EDQueueDataKey : job -// } -// waitUntilDone:false]; - [self postNotificationOnMainThread:@{ EDQueueNameKey : EDQueueJobDidSucceed, EDQueueDataKey : job }]; - [self.engine removeJob:[job objectForKey:@"id"]]; + [self.engine removeJob:job]; break; case EDQueueResultFail: -// [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueJobDidFail, EDQueueNameKey, job, EDQueueDataKey, nil] waitUntilDone:true]; [self postNotificationOnMainThread:@{ EDQueueNameKey : EDQueueJobDidFail, EDQueueDataKey : job }]; - NSUInteger currentAttempt = [[job objectForKey:@"attempts"] intValue] + 1; + NSUInteger currentAttempt = job.attempts.integerValue + 1; if (currentAttempt < self.retryLimit) { - [self.engine incrementAttemptForJob:[job objectForKey:@"id"]]; + [self.engine incrementAttemptForJob:job]; } else { - [self.engine removeJob:[job objectForKey:@"id"]]; + [self.engine removeJob:job]; } break; case EDQueueResultCritical: -// [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueJobDidFail, EDQueueNameKey, job, EDQueueDataKey, nil] waitUntilDone:false]; - [self postNotificationOnMainThread:@{ EDQueueNameKey : EDQueueJobDidFail, EDQueueDataKey : job }]; [self errorWithMessage:@"Critical error. Job canceled."]; - [self.engine removeJob:[job objectForKey:@"id"]]; + [self.engine removeJob:job]; break; } @@ -273,7 +255,7 @@ - (void)processJob:(NSDictionary*)job withResult:(EDQueueResult)result // Drain if ([self.engine fetchJobCount] == 0) { -// [self performSelectorOnMainThread:@selector(postNotification:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:EDQueueDidDrain, EDQueueNameKey, nil, EDQueueDataKey, nil] waitUntilDone:false]; + [self postNotificationOnMainThread:@{ EDQueueNameKey : EDQueueDidDrain, }]; diff --git a/EDQueue/EDQueueJob.h b/EDQueue/EDQueueJob.h new file mode 100644 index 0000000..0d226b0 --- /dev/null +++ b/EDQueue/EDQueueJob.h @@ -0,0 +1,35 @@ +// +// EDQueueJob.h +// queue +// +// Created by Oleg Shanyuk on 18/02/16. +// Copyright © 2016 DIY, Co. All rights reserved. +// + +@import Foundation; + +NS_ASSUME_NONNULL_BEGIN + +@interface EDQueueJob : NSObject + +@property(nonatomic, readonly) NSString *task; +@property(nonatomic, readonly) NSDictionary *userInfo; + +@property(nonatomic, readonly, nullable) NSNumber *jobID; +@property(nonatomic, readonly, nullable) NSNumber *attempts; +@property(nonatomic, readonly, nullable) NSString *timeStamp; + +- (instancetype)initWithTask:(NSString *)task + userInfo:(nullable NSDictionary *)userInfo + jobID:(nullable NSNumber *)jobID + atempts:(nullable NSNumber *)attemps + timeStamp:(nullable NSString *)timeStamp; + +- (instancetype)initWithTask:(NSString *)task + userInfo:(nullable NSDictionary *)userInfo; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/EDQueue/EDQueueJob.m b/EDQueue/EDQueueJob.m new file mode 100644 index 0000000..02a3ee3 --- /dev/null +++ b/EDQueue/EDQueueJob.m @@ -0,0 +1,42 @@ +// +// EDQueueJob.m +// queue +// +// Created by Oleg Shanyuk on 18/02/16. +// Copyright © 2016 DIY, Co. All rights reserved. +// + +#import "EDQueueJob.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation EDQueueJob + +- (instancetype)initWithTask:(NSString *)task + userInfo:(nullable NSDictionary *)userInfo + jobID:(nullable NSNumber *)jobID + atempts:(nullable NSNumber *)attemps + timeStamp:(nullable NSString *)timeStamp +{ + self = [super init]; + + if (self) { + _jobID = [jobID copy]; + _task = [task copy]; + _userInfo = userInfo ? [userInfo copy] : @{}; + _attempts = [attemps copy]; + _timeStamp = [timeStamp copy]; + } + + return self; +} + +- (instancetype)initWithTask:(NSString *)task + userInfo:(nullable NSDictionary *)userInfo +{ + return [self initWithTask:task userInfo:userInfo jobID:nil atempts:nil timeStamp:nil]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/EDQueue/EDQueueStorageEngine.h b/EDQueue/EDQueueStorageEngine.h index 6f84e69..ed2bd8c 100755 --- a/EDQueue/EDQueueStorageEngine.h +++ b/EDQueue/EDQueueStorageEngine.h @@ -6,20 +6,23 @@ // Copyright (c) 2012 DIY, Co. All rights reserved. // -#import +@import Foundation; -@class FMDatabaseQueue; -@interface EDQueueStorageEngine : NSObject +NS_ASSUME_NONNULL_BEGIN + +@class EDQueueJob; -@property (retain) FMDatabaseQueue *queue; +@interface EDQueueStorageEngine : NSObject -- (void)createJob:(NSDictionary *)data forTask:(id)task; -- (BOOL)jobExistsForTask:(id)task; -- (void)incrementAttemptForJob:(NSNumber *)jid; -- (void)removeJob:(NSNumber *)jid; +- (void)createJob:(EDQueueJob *)job; +- (BOOL)jobExistsForTask:(NSString *)task; +- (void)incrementAttemptForJob:(EDQueueJob *)jid; +- (void)removeJob:(EDQueueJob *)jid; - (void)removeAllJobs; - (NSUInteger)fetchJobCount; -- (NSDictionary *)fetchJob; -- (NSDictionary *)fetchJobForTask:(id)task; +- (nullable EDQueueJob *)fetchJob; +- (nullable EDQueueJob *)fetchJobForTask:(NSString *)task; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index c67035e..08a8736 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -13,64 +13,13 @@ #import "FMDatabasePool.h" #import "FMDatabaseQueue.h" -#import "EDQueueStorageJob.h" - - -//static NSString *sql_createTableIfNotExists() -//{ -// return [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS queue (%@ INTEGER PRIMARY KEY, %@ TEXT NOT NULL, %@ TEXT NOT NULL, %@ INTEGER DEFAULT 0, %@ STRING DEFAULT (strftime('%%s','now')) NOT NULL, udef_1 TEXT, udef_2 TEXT)",EDQueueStorageJobIdKey, EDQueueStorageJobTaskKey, EDQueueStorageJobDataKey, EDQueueStorageJobAttemptsKey, EDQueueStorageJobStampKey]; -//} -// -//static NSString *sql_createJob() -//{ -// return [NSString stringWithFormat:@"INSERT INTO queue (%@, %@) VALUES (?, ?)", EDQueueStorageJobTaskKey, EDQueueStorageJobDataKey ]; -//} -// -//static NSString *sql_deleteJob() -//{ -// return [NSString stringWithFormat:@"DELETE FROM queue WHERE %@ = ?", EDQueueStorageJobIdKey]; -//} -// -//static NSString *sql_jobExistsForTask; -//static NSString *sql_incrementAttemptForJob; -//static NSString *sql_fetchJobCount; -//static NSString *sql_fetchJob; -//static NSString *sql_fetchJobForTask; -//static NSString *sql_deleteAllJobs; - -@interface EDQueueStorageJob(FMResultSet) - --(instancetype)initWithFMResultSet:(FMResultSet *)resutSet; - -@end - -@implementation EDQueueStorageJob(FMResultSet) - -- (instancetype)initWithFMResultSet:(FMResultSet *)rs -{ - NSString *json = [rs stringForColumn:@"data"]; - - NSString *data = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding] - options:NSJSONReadingMutableContainers - error:nil]; - - NSDictionary *job = @{ - EDQueueStorageJobIdKey : [NSNumber numberWithInt:[rs intForColumn:@"id"]], - EDQueueStorageJobTaskKey : [rs stringForColumn:@"task"], - EDQueueStorageJobTaskKey : data, - EDQueueStorageJobAttemptsKey: [NSNumber numberWithInt:[rs intForColumn:@"attempts"]], - EDQueueStorageJobStampKey: [rs stringForColumn:@"stamp"] - }; - - return [self initWithDictionary:job]; -} - -@end - +#import "EDQueueJob.h" +NS_ASSUME_NONNULL_BEGIN @interface EDQueueStorageEngine() +@property (retain) FMDatabaseQueue *queue; @end @@ -117,12 +66,21 @@ - (void)dealloc * * @return {void} */ -- (void)createJob:(id)data forTask:(id)task +//- (void)createJob:(id)data forTask:(id)task +- (void)createJob:(EDQueueJob *)job { - NSString *dataString = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:data options:NSJSONWritingPrettyPrinted error:nil] encoding:NSUTF8StringEncoding]; + NSString *dataString = nil; + + if (job.userInfo) { + NSData *data = [NSJSONSerialization dataWithJSONObject:job.userInfo + options:NSJSONWritingPrettyPrinted + error:nil]; + + dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + } [self.queue inDatabase:^(FMDatabase *db) { - [db executeUpdate:@"INSERT INTO queue (task, data) VALUES (?, ?)", task, dataString]; + [db executeUpdate:@"INSERT INTO queue (task, data) VALUES (?, ?)", job.task, dataString]; [self _databaseHadError:[db hadError] fromDatabase:db]; }]; } @@ -134,7 +92,7 @@ - (void)createJob:(id)data forTask:(id)task * * @return {BOOL} */ -- (BOOL)jobExistsForTask:(id)task +- (BOOL)jobExistsForTask:(NSString *)task { __block BOOL jobExists = NO; @@ -159,10 +117,14 @@ - (BOOL)jobExistsForTask:(id)task * * @return {void} */ -- (void)incrementAttemptForJob:(NSNumber *)jid +- (void)incrementAttemptForJob:(EDQueueJob *)job { + if (!job.jobID) { + return; + } + [self.queue inDatabase:^(FMDatabase *db) { - [db executeUpdate:@"UPDATE queue SET attempts = attempts + 1 WHERE id = ?", jid]; + [db executeUpdate:@"UPDATE queue SET attempts = attempts + 1 WHERE id = ?", job.jobID]; [self _databaseHadError:[db hadError] fromDatabase:db]; }]; } @@ -174,10 +136,14 @@ - (void)incrementAttemptForJob:(NSNumber *)jid * * @return {void} */ -- (void)removeJob:(NSNumber *)jid +- (void)removeJob:(EDQueueJob *)job { + if (!job.jobID) { + return; + } + [self.queue inDatabase:^(FMDatabase *db) { - [db executeUpdate:@"DELETE FROM queue WHERE id = ?", jid]; + [db executeUpdate:@"DELETE FROM queue WHERE id = ?", job.jobID]; [self _databaseHadError:[db hadError] fromDatabase:db]; }]; } @@ -223,9 +189,9 @@ - (NSUInteger)fetchJobCount * * @return {NSDictionary} */ -- (NSDictionary *)fetchJob +- (nullable EDQueueJob *)fetchJob { - __block id job; + __block EDQueueJob *job; [self.queue inDatabase:^(FMDatabase *db) { FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue ORDER BY id ASC LIMIT 1"]; @@ -248,9 +214,9 @@ - (NSDictionary *)fetchJob * * @return {NSDictionary} */ -- (NSDictionary *)fetchJobForTask:(id)task +- (nullable EDQueueJob *)fetchJobForTask:(NSString *)task { - __block id job; + __block EDQueueJob *job; [self.queue inDatabase:^(FMDatabase *db) { FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE task = ? ORDER BY id ASC LIMIT 1", task]; @@ -268,15 +234,27 @@ - (NSDictionary *)fetchJobForTask:(id)task #pragma mark - Private methods -- (NSDictionary *)_jobFromResultSet:(FMResultSet *)rs +- (EDQueueJob *)_jobFromResultSet:(FMResultSet *)rs { - NSDictionary *job = @{ - @"id": [NSNumber numberWithInt:[rs intForColumn:@"id"]], - @"task": [rs stringForColumn:@"task"], - @"data": [NSJSONSerialization JSONObjectWithData:[[rs stringForColumn:@"data"] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil], - @"attempts": [NSNumber numberWithInt:[rs intForColumn:@"attempts"]], - @"stamp": [rs stringForColumn:@"stamp"] - }; + NSDictionary *userInfo = [NSJSONSerialization JSONObjectWithData:[[rs stringForColumn:@"data"] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil]; + + +// NSDictionary *job = @{ +// @"id": [NSNumber numberWithInt:[rs intForColumn:@"id"]], +// @"task" : [rs stringForColumn:@"task"], +// @"userInfo" : slob, +// @"attempts" : [NSNumber numberWithInt:[rs intForColumn:@"attempts"]], +// @"stamp": [rs stringForColumn:@"stamp"] +// }; + + EDQueueJob *job = [[EDQueueJob alloc] initWithTask:[rs stringForColumn:@"task"] + userInfo:userInfo + jobID:@([rs intForColumn:@"id"]) + atempts:@([rs intForColumn:@"attempts"]) + timeStamp:[rs stringForColumn:@"stamp"]]; + + + return job; } @@ -287,3 +265,5 @@ - (BOOL)_databaseHadError:(BOOL)flag fromDatabase:(FMDatabase *)db } @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/EDQueue/EDQueueStorageJob.h b/EDQueue/EDQueueStorageJob.h deleted file mode 100644 index d2776f6..0000000 --- a/EDQueue/EDQueueStorageJob.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// EDQueueJob.h -// queue -// -// Created by Oleg Shanyuk on 18/02/16. -// Copyright © 2016 DIY, Co. All rights reserved. -// - -#import - - -extern NSString *const EDQueueStorageJobIdKey; -extern NSString *const EDQueueStorageJobTaskKey; -extern NSString *const EDQueueStorageJobDataKey; -extern NSString *const EDQueueStorageJobAttemptsKey; -extern NSString *const EDQueueStorageJobStampKey; - -@interface EDQueueStorageJob : NSObject - -@property (nonatomic, readonly) NSNumber *jobId; -@property (nonatomic, readonly) NSString *task; -@property (nonatomic, readonly) NSData *data; -@property (nonatomic, readonly) NSNumber* attempts; -@property (nonatomic, readonly) NSString *stamp; -@property (nonatomic, readonly) NSDictionary *dictionaryRepresentation; - -- (instancetype)initWithDictionary:(NSDictionary *)dictionary; -- (instancetype)init NS_UNAVAILABLE; - -- (NSDictionary*)dictionaryRepresentation; - -@end diff --git a/EDQueue/EDQueueStorageJob.m b/EDQueue/EDQueueStorageJob.m deleted file mode 100644 index 57c642c..0000000 --- a/EDQueue/EDQueueStorageJob.m +++ /dev/null @@ -1,45 +0,0 @@ -// -// EDQueueJob.m -// queue -// -// Created by Oleg Shanyuk on 18/02/16. -// Copyright © 2016 DIY, Co. All rights reserved. -// - -#import "EDQueueStorageJob.h" - -NSString *const EDQueueStorageJobIdKey = @"id"; -NSString *const EDQueueStorageJobTaskKey = @"task"; -NSString *const EDQueueStorageJobDataKey = @"data"; -NSString *const EDQueueStorageJobAttemptsKey = @"atempts"; -NSString *const EDQueueStorageJobStampKey = @"stamp"; - -@implementation EDQueueStorageJob - -- (instancetype)initWithDictionary:(NSDictionary *)dictionary -{ - self = [super init]; - - if (self) { - _jobId = [dictionary[EDQueueStorageJobIdKey] copy]; - _task = [dictionary[EDQueueStorageJobTaskKey] copy]; - _data = [dictionary[EDQueueStorageJobDataKey] copy]; - _attempts = [dictionary[EDQueueStorageJobAttemptsKey] copy]; - _stamp = [dictionary[EDQueueStorageJobStampKey] copy]; - } - - return self; -} - -- (NSDictionary *)dictionaryRepresentation -{ - return @{ - EDQueueStorageJobIdKey : _jobId, - EDQueueStorageJobTaskKey : _task, - EDQueueStorageJobDataKey : _data, - EDQueueStorageJobAttemptsKey : _attempts, - EDQueueStorageJobStampKey : _stamp - }; -} - -@end diff --git a/Project/Default-568h@2x.png b/Project/Default-568h@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0891b7aabfcf3422423b109c8beed2bab838c607 GIT binary patch literal 18594 zcmeI4X;f257Jx&9fS`ixvS;&$x8J@slQFSel)6zJN=?13FB7H(lQjRkSy8x_-S~tvu2gzn1oS+dLcF#eqtq$ z%tf9TTvX?`)R@}3uBI;jzS-=ZR-Td&MHaS&;!0?Ni*#$#`n*~CcQK)Q9vAQ~TUpnI!j)a2biYK^R)M~A5wUDZhx?ULMX z3x1P&qt=trOY6P2U67L=m=U?F|5#Uj(eCueNTZaHs_ceWiHeET+j+tp3Jt9g(ekqP z2WOvfR{qV+9r+o4J5?qK>7;;^+I7tGv-i)es$X_D=EoKF+S?zsyj^oRFElP}c}JT< zd8SUs-?O?}2YD#ngKbnHgzHBcboxK_2r9l(?eNCl-pEzkJm}fY?WC*jnS?VBE4EpY zO$fEejz6fU;W2Kl>JeQBZBl-%Irg`obSlg*@4QB;Dd1H7^Oi5wvt4d{RZ!8Og?^aE z)k0$1g+V3fd(gdQ3d&q2q-FL*uy#}|bc^=VhFsl0jBgUGJ+-s3U8MK9A!YJJMxpci z5hJ%|{DwV48fZn0{n5l$N_KcSb#NKE4plB`9I6Zt=Z!~-zw0{9tg$L&Ju1F0X)Cy8 zKF;(&lJ>x)Jw(=;p~sF(Sd9VWGwFE2rnyS9!f^DZ8+aCLq zQ};>lcJ1GDLqjm6Hd>|Eabno@P`~Bn(~6^aD_#yoEH(a?Nm1S<;S+hSxI5d16^<1lEM3NPFi zkqPrpL)+ zgnseFikg`gJVBha1&7C4;O6>h=dt~`ND+;Zd?W(4v2JIb7Pt>Td42%M-Ju-XAH#Pns762L}K3 zDhvsRqN0Ni(1UrishD2YvV?4*h2iFj$+&N||Fn$4n|^NSU+o?~jq`0jVQt8T9l{7b zXiwwODFh2V!Q6sqP9S>WH$oOf$N~=d0-bqTlD61!=`&0eAP-F>XN?*|gtOXX{ zQVTWyYo4ZK0GAw!GHf|pz9`D;-bbb*5LBX*{bnz|+)$@&P9|ORM2o?95{;ejvo&r- zq8cBhTN6nn)7~W>54U)%-F_-b?YKdfk5I8MHcuzBD5)!;yv#Z&R&^y=@=>VTIMy#r zX&U<=BsPkdqcMe<_}2+>H%XKyrr5ZR8_KVe>ZqYN z^=^~TFD};;rHJ$U;{~w^hYojl4hRI@SH$^K{YEo=sg)WY87r!*7blQK&qnpDo0`Vn zkl)9u9g=mCh&ZCJS(L4yN3k0kQ zuvg$h2KEEk51T+O0JQ+r0`R>g{jvqM0Mr6d3qUOZwE!?PI7HY@CE|dr sfw?Q;rAv?G4&^^8-z_>&sWXMxvD*gPOU4CBe-*@OtE+wfmVJNyHv)PfH~;_u literal 0 HcmV?d00001 diff --git a/Project/queue.xcodeproj/project.pbxproj b/Project/queue.xcodeproj/project.pbxproj index 65ba684..bed54cf 100644 --- a/Project/queue.xcodeproj/project.pbxproj +++ b/Project/queue.xcodeproj/project.pbxproj @@ -7,7 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - 7CA53AF41C75F88E00420814 /* EDQueueStorageJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CA53AF31C75F88E00420814 /* EDQueueStorageJob.m */; }; + 7CA53AF41C75F88E00420814 /* EDQueueJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CA53AF31C75F88E00420814 /* EDQueueJob.m */; }; + 7CA53AF61C7733E400420814 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 7CA53AF51C7733E400420814 /* Default-568h@2x.png */; }; C32F6E07160795C3004BA8A1 /* EDQueue.podspec in Resources */ = {isa = PBXBuildFile; fileRef = C32F6E06160795C3004BA8A1 /* EDQueue.podspec */; }; C32F6E0C16079680004BA8A1 /* libsqlite3.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C32F6E0B16079680004BA8A1 /* libsqlite3.0.dylib */; }; C32F6E191607AC35004BA8A1 /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = C32F6E101607AC35004BA8A1 /* FMDatabase.m */; }; @@ -30,8 +31,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 7CA53AF21C75F88E00420814 /* EDQueueStorageJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EDQueueStorageJob.h; path = ../EDQueue/EDQueueStorageJob.h; sourceTree = ""; }; - 7CA53AF31C75F88E00420814 /* EDQueueStorageJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EDQueueStorageJob.m; path = ../EDQueue/EDQueueStorageJob.m; sourceTree = ""; }; + 7CA53AF21C75F88E00420814 /* EDQueueJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EDQueueJob.h; path = ../EDQueue/EDQueueJob.h; sourceTree = ""; }; + 7CA53AF31C75F88E00420814 /* EDQueueJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EDQueueJob.m; path = ../EDQueue/EDQueueJob.m; sourceTree = ""; }; + 7CA53AF51C7733E400420814 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; C32F6E06160795C3004BA8A1 /* EDQueue.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = EDQueue.podspec; path = ../EDQueue.podspec; sourceTree = ""; }; C32F6E0B16079680004BA8A1 /* libsqlite3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.0.dylib; path = usr/lib/libsqlite3.0.dylib; sourceTree = SDKROOT; }; C32F6E0F1607AC35004BA8A1 /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = ""; }; @@ -87,8 +89,8 @@ C395D533159E57880041510C /* EDQueue.m */, C32F6E221607E1FB004BA8A1 /* EDQueueStorageEngine.h */, C32F6E231607E1FB004BA8A1 /* EDQueueStorageEngine.m */, - 7CA53AF21C75F88E00420814 /* EDQueueStorageJob.h */, - 7CA53AF31C75F88E00420814 /* EDQueueStorageJob.m */, + 7CA53AF21C75F88E00420814 /* EDQueueJob.h */, + 7CA53AF31C75F88E00420814 /* EDQueueJob.m */, ); name = EDQueue; sourceTree = ""; @@ -121,6 +123,7 @@ C395D4FD159E56760041510C = { isa = PBXGroup; children = ( + 7CA53AF51C7733E400420814 /* Default-568h@2x.png */, C395D52D159E57520041510C /* LICENSE.md */, C395D52E159E57520041510C /* README.md */, C32F6E06160795C3004BA8A1 /* EDQueue.podspec */, @@ -203,7 +206,7 @@ isa = PBXProject; attributes = { CLASSPREFIX = ED; - LastUpgradeCheck = 0460; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = "DIY, Co."; }; buildConfigurationList = C395D502159E56760041510C /* Build configuration list for PBXProject "queue" */; @@ -233,6 +236,7 @@ C395D52F159E57520041510C /* LICENSE.md in Resources */, C395D530159E57520041510C /* README.md in Resources */, C32F6E07160795C3004BA8A1 /* EDQueue.podspec in Resources */, + 7CA53AF61C7733E400420814 /* Default-568h@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -243,7 +247,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7CA53AF41C75F88E00420814 /* EDQueueStorageJob.m in Sources */, + 7CA53AF41C75F88E00420814 /* EDQueueJob.m in Sources */, C395D519159E56760041510C /* main.m in Sources */, C395D51D159E56760041510C /* EDAppDelegate.m in Sources */, C395D520159E56760041510C /* EDViewController.m in Sources */, @@ -283,15 +287,21 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -299,10 +309,14 @@ ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 5.1; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; @@ -311,19 +325,27 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 5.1; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; @@ -333,10 +355,13 @@ C395D527159E56760041510C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "queue/queue-Prefix.pch"; INFOPLIST_FILE = "queue/queue-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.diy.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -345,10 +370,13 @@ C395D528159E56760041510C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "queue/queue-Prefix.pch"; INFOPLIST_FILE = "queue/queue-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.diy.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; diff --git a/Project/queue/EDAppDelegate.m b/Project/queue/EDAppDelegate.m index 385978f..9463890 100644 --- a/Project/queue/EDAppDelegate.m +++ b/Project/queue/EDAppDelegate.m @@ -37,14 +37,14 @@ - (void)applicationWillResignActive:(UIApplication *)application [[EDQueue sharedInstance] stop]; } -- (void)queue:(EDQueue *)queue processJob:(NSDictionary *)job completion:(void (^)(EDQueueResult))block +- (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(void (^)(EDQueueResult))block { sleep(1); @try { - if ([[job objectForKey:@"task"] isEqualToString:@"success"]) { + if ([job.task isEqualToString:@"success"]) { block(EDQueueResultSuccess); - } else if ([[job objectForKey:@"task"] isEqualToString:@"fail"]) { + } else if ([job.task isEqualToString:@"fail"]) { block(EDQueueResultFail); } else { block(EDQueueResultCritical); @@ -54,27 +54,6 @@ - (void)queue:(EDQueue *)queue processJob:(NSDictionary *)job completion:(void ( block(EDQueueResultCritical); } } - -//- (EDQueueResult)queue:(EDQueue *)queue processJob:(NSDictionary *)job -//{ -// sleep(1); -// -// @try { -// if ([[job objectForKey:@"task"] isEqualToString:@"success"]) { -// return EDQueueResultSuccess; -// } else if ([[job objectForKey:@"task"] isEqualToString:@"fail"]) { -// return EDQueueResultFail; -// } -// } -// @catch (NSException *exception) { -// return EDQueueResultCritical; -// } -// -// return EDQueueResultCritical; -//} - -// - - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. diff --git a/Project/queue/EDViewController.m b/Project/queue/EDViewController.m index aa1a8b9..233d12e 100644 --- a/Project/queue/EDViewController.m +++ b/Project/queue/EDViewController.m @@ -36,17 +36,20 @@ - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interface - (IBAction)addSuccess:(id)sender { - [[EDQueue sharedInstance] enqueueWithData:@{ @"nyan" : @"cat" } forTask:@"success"]; + EDQueueJob *success = [[EDQueueJob alloc] initWithTask:@"success" userInfo:@{ @"nyan" : @"cat" }]; + [[EDQueue sharedInstance] enqueueJob:success]; } - (IBAction)addFail:(id)sender { - [[EDQueue sharedInstance] enqueueWithData:nil forTask:@"fail"]; + EDQueueJob *success = [[EDQueueJob alloc] initWithTask:@"fail" userInfo:nil]; + [[EDQueue sharedInstance] enqueueJob:success]; } - (IBAction)addCritical:(id)sender { - [[EDQueue sharedInstance] enqueueWithData:nil forTask:@"critical"]; + EDQueueJob *success = [[EDQueueJob alloc] initWithTask:@"critical" userInfo:nil]; + [[EDQueue sharedInstance] enqueueJob:success]; } #pragma mark - Notifications diff --git a/Project/queue/queue-Info.plist b/Project/queue/queue-Info.plist index 36f966e..3eb7025 100644 --- a/Project/queue/queue-Info.plist +++ b/Project/queue/queue-Info.plist @@ -9,7 +9,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.diy.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName From 9adb89e4fdb4cec68a4cc920a8da2c633c24c8e2 Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Fri, 19 Feb 2016 16:04:53 +0100 Subject: [PATCH 03/14] Introduced Persistent Storage Injection for Queue Updated Code & Example Added basic Queue tests README updated accordingly --- EDQueue.podspec | 4 +- EDQueue/EDQueue.h | 28 ++-- EDQueue/EDQueue.m | 70 ++------- EDQueue/EDQueuePersistentStorageProtocol.h | 30 ++++ EDQueue/EDQueueStorageEngine.h | 16 +-- EDQueue/EDQueueStorageEngine.m | 49 +++---- Project/queue.xcodeproj/project.pbxproj | 136 ++++++++++++++++++ Project/queue/EDAppDelegate.h | 8 +- Project/queue/EDAppDelegate.m | 17 ++- Project/queue/EDViewController.h | 4 +- Project/queue/EDViewController.m | 10 +- Project/queueTests/EDQueueTests.m | 156 +++++++++++++++++++++ Project/queueTests/Info.plist | 24 ++++ README.md | 61 +++++--- 14 files changed, 475 insertions(+), 138 deletions(-) create mode 100644 EDQueue/EDQueuePersistentStorageProtocol.h create mode 100644 Project/queueTests/EDQueueTests.m create mode 100644 Project/queueTests/Info.plist diff --git a/EDQueue.podspec b/EDQueue.podspec index 5024c1b..21fe78a 100644 --- a/EDQueue.podspec +++ b/EDQueue.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |s| s.name = 'EDQueue' - s.version = '0.7.2' + s.version = '0.7.3' s.license = 'MIT' s.summary = 'A persistent background job queue for iOS.' s.homepage = 'https://github.com/gelosi/queue' s.authors = {'Andrew Sliwinski' => 'andrewsliwinski@acm.org', 'Francois Lambert' => 'flambert@mirego.com', 'Oleg Shanyuk' => 'oleg.shanyuk@gmail.com'} - s.source = { :git => 'https://github.com/gelosi/queue.git', :tag => 'v0.7.2' } + s.source = { :git => 'https://github.com/gelosi/queue.git', :tag => 'v0.7.3' } s.platform = :ios, '7.0' s.source_files = 'EDQueue' s.library = 'sqlite3.0' diff --git a/EDQueue/EDQueue.h b/EDQueue/EDQueue.h index 30183c2..a8a986c 100755 --- a/EDQueue/EDQueue.h +++ b/EDQueue/EDQueue.h @@ -6,9 +6,10 @@ // Copyright (c) 2012 Andrew Sliwinski. All rights reserved. // -#import +@import Foundation; #import "EDQueueJob.h" +#import "EDQueuePersistentStorageProtocol.h" NS_ASSUME_NONNULL_BEGIN @@ -26,22 +27,32 @@ extern NSString *const EDQueueJobDidSucceed; extern NSString *const EDQueueJobDidFail; extern NSString *const EDQueueDidDrain; -extern NSString *const EDQueueNameKey; -extern NSString *const EDQueueDataKey; +@class EDQueue; - -@protocol EDQueueDelegate; +@protocol EDQueueDelegate +- (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(EDQueueCompletionBlock)block; +@end @interface EDQueue : NSObject -+ (EDQueue *)sharedInstance; - @property (nonatomic, weak) id delegate; +@property (nonatomic, strong, readonly) id storage; +/** + * Returns true if Queue is running (e.g. not stopped). + */ @property (nonatomic, readonly) BOOL isRunning; +/** + * Returns true if Queue is performing Job right now + */ @property (nonatomic, readonly) BOOL isActive; +/** + * Retry limit for failing tasks (will be elimitated and moved to Job later) + */ @property (nonatomic) NSUInteger retryLimit; +- (instancetype)initWithPersistentStore:(id)persistentStore; + - (void)enqueueJob:(EDQueueJob *)job; - (void)start; - (void)stop; @@ -53,8 +64,5 @@ extern NSString *const EDQueueDataKey; @end -@protocol EDQueueDelegate -- (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(EDQueueCompletionBlock)block; -@end NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index 557c782..2ae2f3f 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -15,68 +15,31 @@ NSString *const EDQueueJobDidFail = @"EDQueueJobDidFail"; NSString *const EDQueueDidDrain = @"EDQueueDidDrain"; -NSString *const EDQueueNameKey = @"name"; -NSString *const EDQueueDataKey = @"data"; - - -NSString *const EDQueueStorageJobIdKey = @"id"; -NSString *const EDQueueStorageJobTaskKey = @"task"; -NSString *const EDQueueStorageJobDataKey = @"data"; -NSString *const EDQueueStorageJobAttemptsKey = @"atempts"; -NSString *const EDQueueStorageJobStampKey = @"stamp"; +static NSString *const EDQueueNameKey = @"name"; +static NSString *const EDQueueDataKey = @"data"; NS_ASSUME_NONNULL_BEGIN @interface EDQueue () -{ - BOOL _isRunning; - BOOL _isActive; - NSUInteger _retryLimit; -} -@property (nonatomic) EDQueueStorageEngine *engine; @property (nonatomic, readwrite, nullable) NSString *activeTask; @end -// @implementation EDQueue -@synthesize isRunning = _isRunning; -@synthesize isActive = _isActive; -@synthesize retryLimit = _retryLimit; - -#pragma mark - Singleton - -+ (EDQueue *)sharedInstance -{ - static EDQueue *singleton = nil; - static dispatch_once_t once = 0; - dispatch_once(&once, ^{ - singleton = [[self alloc] init]; - }); - return singleton; -} - -#pragma mark - Init - -- (id)init +- (instancetype)initWithPersistentStore:(id)persistentStore { self = [super init]; if (self) { - _engine = [[EDQueueStorageEngine alloc] init]; _retryLimit = 4; + _storage = persistentStore; } return self; } -- (void)dealloc -{ - self.delegate = nil; - _engine = nil; -} #pragma mark - Public methods @@ -90,7 +53,7 @@ - (void)dealloc */ - (void)enqueueJob:(EDQueueJob *)job { - [self.engine createJob:job]; + [self.storage createJob:job]; [self tick]; } @@ -103,7 +66,7 @@ - (void)enqueueJob:(EDQueueJob *)job */ - (BOOL)jobExistsForTask:(NSString *)task { - BOOL jobExists = [self.engine jobExistsForTask:task]; + BOOL jobExists = [self.storage jobExistsForTask:task]; return jobExists; } @@ -129,7 +92,7 @@ - (BOOL)jobIsActiveForTask:(NSString *)task */ - (nullable EDQueueJob *)nextJobForTask:(NSString *)task { - EDQueueJob *nextJobForTask = [self.engine fetchJobForTask:task]; + EDQueueJob *nextJobForTask = [self.storage fetchNextJobForTask:task]; return nextJobForTask; } @@ -146,7 +109,6 @@ - (void)start NSDictionary *object = @{ EDQueueNameKey : EDQueueDidStart }; -// [self performSelectorOnMainThread:@selector(postNotificationOnMainThread:) withObject:object waitUntilDone:NO]; [self postNotificationOnMainThread:object]; } } @@ -163,8 +125,6 @@ - (void)stop _isRunning = NO; NSDictionary *object = @{ EDQueueNameKey : EDQueueDidStop }; - -// [self performSelectorOnMainThread:@selector(postNotification:) withObject:object waitUntilDone:NO]; [self postNotificationOnMainThread:object]; } } @@ -179,7 +139,7 @@ - (void)stop */ - (void)empty { - [self.engine removeAllJobs]; + [self.storage removeAllJobs]; } @@ -194,10 +154,10 @@ - (void)tick { dispatch_queue_t gcd = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); dispatch_async(gcd, ^{ - if (self.isRunning && !self.isActive && [self.engine fetchJobCount] > 0) { + if (self.isRunning && !self.isActive && [self.storage jobCount] > 0) { // Start job _isActive = YES; - EDQueueJob *job = [self.engine fetchJob]; + EDQueueJob *job = [self.storage fetchNextJob]; self.activeTask = job.task; // Pass job to delegate @@ -220,7 +180,7 @@ - (void)processJob:(EDQueueJob*)job withResult:(EDQueueResult)result EDQueueDataKey : job }]; - [self.engine removeJob:job]; + [self.storage removeJob:job]; break; case EDQueueResultFail: @@ -233,9 +193,9 @@ - (void)processJob:(EDQueueJob*)job withResult:(EDQueueResult)result NSUInteger currentAttempt = job.attempts.integerValue + 1; if (currentAttempt < self.retryLimit) { - [self.engine incrementAttemptForJob:job]; + [self.storage incrementAttemptForJob:job]; } else { - [self.engine removeJob:job]; + [self.storage removeJob:job]; } break; case EDQueueResultCritical: @@ -246,7 +206,7 @@ - (void)processJob:(EDQueueJob*)job withResult:(EDQueueResult)result }]; [self errorWithMessage:@"Critical error. Job canceled."]; - [self.engine removeJob:job]; + [self.storage removeJob:job]; break; } @@ -254,7 +214,7 @@ - (void)processJob:(EDQueueJob*)job withResult:(EDQueueResult)result _isActive = NO; // Drain - if ([self.engine fetchJobCount] == 0) { + if ([self.storage jobCount] == 0) { [self postNotificationOnMainThread:@{ EDQueueNameKey : EDQueueDidDrain, diff --git a/EDQueue/EDQueuePersistentStorageProtocol.h b/EDQueue/EDQueuePersistentStorageProtocol.h new file mode 100644 index 0000000..f62998e --- /dev/null +++ b/EDQueue/EDQueuePersistentStorageProtocol.h @@ -0,0 +1,30 @@ +// +// EDQueuePersistentStorageProtocol.h +// queue +// +// Created by Oleg Shanyuk on 19/02/16. +// Copyright © 2016 DIY, Co. All rights reserved. +// + +@import Foundation; + +NS_ASSUME_NONNULL_BEGIN + +@class EDQueueJob; + +@protocol EDQueuePersistentStorage + +- (void)createJob:(EDQueueJob *)job; +- (BOOL)jobExistsForTask:(NSString *)task; +- (void)incrementAttemptForJob:(EDQueueJob *)jid; + +- (void)removeJob:(EDQueueJob *)jid; +- (void)removeAllJobs; + +- (NSUInteger)jobCount; +- (nullable EDQueueJob *)fetchNextJob; +- (nullable EDQueueJob *)fetchNextJobForTask:(NSString *)task; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/EDQueue/EDQueueStorageEngine.h b/EDQueue/EDQueueStorageEngine.h index ed2bd8c..9afef0d 100755 --- a/EDQueue/EDQueueStorageEngine.h +++ b/EDQueue/EDQueueStorageEngine.h @@ -8,20 +8,18 @@ @import Foundation; +#import "EDQueuePersistentStorageProtocol.h" + NS_ASSUME_NONNULL_BEGIN @class EDQueueJob; -@interface EDQueueStorageEngine : NSObject +@interface EDQueueStorageEngine : NSObject + +- (nullable instancetype)initWithName:(NSString *)name; +- (instancetype)init NS_UNAVAILABLE; -- (void)createJob:(EDQueueJob *)job; -- (BOOL)jobExistsForTask:(NSString *)task; -- (void)incrementAttemptForJob:(EDQueueJob *)jid; -- (void)removeJob:(EDQueueJob *)jid; -- (void)removeAllJobs; -- (NSUInteger)fetchJobCount; -- (nullable EDQueueJob *)fetchJob; -- (nullable EDQueueJob *)fetchJobForTask:(NSString *)task; ++ (void)deleteDatabaseName:(NSString *)name; @end diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index 08a8736..442437f 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -17,6 +17,15 @@ NS_ASSUME_NONNULL_BEGIN +static NSString *pathForStorageName(NSString *storage) +{ + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *path = [documentsDirectory stringByAppendingPathComponent:storage]; + + return path; +} + @interface EDQueueStorageEngine() @property (retain) FMDatabaseQueue *queue; @@ -25,22 +34,24 @@ @interface EDQueueStorageEngine() @implementation EDQueueStorageEngine +#pragma mark - Class + ++ (void)deleteDatabaseName:(NSString *)name +{ + [[NSFileManager defaultManager] removeItemAtPath:pathForStorageName(name) error:nil]; +} + #pragma mark - Init -- (id)init +- (nullable instancetype)initWithName:(NSString *)name { self = [super init]; if (self) { - // Database path - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - NSString *path = [documentsDirectory stringByAppendingPathComponent:@"edqueue_0.5.0d.db"]; - - // Allocate the queue - _queue = [[FMDatabaseQueue alloc] initWithPath:path]; + _queue = [[FMDatabaseQueue alloc] initWithPath:pathForStorageName(name)]; - -// NSString *sql = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS queue (%@ INTEGER PRIMARY KEY, %@ TEXT NOT NULL, %@ TEXT NOT NULL, %@ INTEGER DEFAULT 0, %@ STRING DEFAULT (strftime('%%s','now')) NOT NULL, udef_1 TEXT, udef_2 TEXT)",EDQueueStorageJobIdKey, EDQueueStorageJobTaskKey, EDQueueStorageJobDataKey, EDQueueStorageJobAttemptsKey, EDQueueStorageJobStampKey]; + if (!_queue) { + return nil; + } [self.queue inDatabase:^(FMDatabase *db) { [db executeUpdate:@"CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY, task TEXT NOT NULL, data TEXT NOT NULL, attempts INTEGER DEFAULT 0, stamp STRING DEFAULT (strftime('%s','now')) NOT NULL, udef_1 TEXT, udef_2 TEXT)"]; @@ -61,8 +72,7 @@ - (void)dealloc /** * Creates a new job within the datastore. * - * @param {NSString} Data (JSON string) - * @param {NSString} Task name + * @param {EDQueueJob} a Job * * @return {void} */ @@ -166,7 +176,7 @@ - (void)removeAllJobs { * * @return {uint} */ -- (NSUInteger)fetchJobCount +- (NSUInteger)jobCount { __block NSUInteger count = 0; @@ -189,7 +199,7 @@ - (NSUInteger)fetchJobCount * * @return {NSDictionary} */ -- (nullable EDQueueJob *)fetchJob +- (nullable EDQueueJob *)fetchNextJob { __block EDQueueJob *job; @@ -214,7 +224,7 @@ - (nullable EDQueueJob *)fetchJob * * @return {NSDictionary} */ -- (nullable EDQueueJob *)fetchJobForTask:(NSString *)task +- (nullable EDQueueJob *)fetchNextJobForTask:(NSString *)task { __block EDQueueJob *job; @@ -238,15 +248,6 @@ - (EDQueueJob *)_jobFromResultSet:(FMResultSet *)rs { NSDictionary *userInfo = [NSJSONSerialization JSONObjectWithData:[[rs stringForColumn:@"data"] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil]; - -// NSDictionary *job = @{ -// @"id": [NSNumber numberWithInt:[rs intForColumn:@"id"]], -// @"task" : [rs stringForColumn:@"task"], -// @"userInfo" : slob, -// @"attempts" : [NSNumber numberWithInt:[rs intForColumn:@"attempts"]], -// @"stamp": [rs stringForColumn:@"stamp"] -// }; - EDQueueJob *job = [[EDQueueJob alloc] initWithTask:[rs stringForColumn:@"task"] userInfo:userInfo jobID:@([rs intForColumn:@"id"]) diff --git a/Project/queue.xcodeproj/project.pbxproj b/Project/queue.xcodeproj/project.pbxproj index bed54cf..d71b9ad 100644 --- a/Project/queue.xcodeproj/project.pbxproj +++ b/Project/queue.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 7CA53AF41C75F88E00420814 /* EDQueueJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CA53AF31C75F88E00420814 /* EDQueueJob.m */; }; 7CA53AF61C7733E400420814 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 7CA53AF51C7733E400420814 /* Default-568h@2x.png */; }; + 7CA53B061C77496800420814 /* EDQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CA53B051C77496800420814 /* EDQueueTests.m */; }; C32F6E07160795C3004BA8A1 /* EDQueue.podspec in Resources */ = {isa = PBXBuildFile; fileRef = C32F6E06160795C3004BA8A1 /* EDQueue.podspec */; }; C32F6E0C16079680004BA8A1 /* libsqlite3.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C32F6E0B16079680004BA8A1 /* libsqlite3.0.dylib */; }; C32F6E191607AC35004BA8A1 /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = C32F6E101607AC35004BA8A1 /* FMDatabase.m */; }; @@ -30,10 +31,24 @@ C395D534159E57880041510C /* EDQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = C395D533159E57880041510C /* EDQueue.m */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 7CA53B001C77495200420814 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C395D4FF159E56760041510C /* Project object */; + proxyType = 1; + remoteGlobalIDString = C395D507159E56760041510C; + remoteInfo = queue; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 7CA53AF21C75F88E00420814 /* EDQueueJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EDQueueJob.h; path = ../EDQueue/EDQueueJob.h; sourceTree = ""; }; 7CA53AF31C75F88E00420814 /* EDQueueJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EDQueueJob.m; path = ../EDQueue/EDQueueJob.m; sourceTree = ""; }; 7CA53AF51C7733E400420814 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; + 7CA53AFB1C77495200420814 /* queueTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = queueTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 7CA53AFF1C77495200420814 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7CA53B051C77496800420814 /* EDQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EDQueueTests.m; sourceTree = ""; }; + 7CA53B071C774EE900420814 /* EDQueuePersistentStorageProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EDQueuePersistentStorageProtocol.h; path = ../EDQueue/EDQueuePersistentStorageProtocol.h; sourceTree = ""; }; C32F6E06160795C3004BA8A1 /* EDQueue.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = EDQueue.podspec; path = ../EDQueue.podspec; sourceTree = ""; }; C32F6E0B16079680004BA8A1 /* libsqlite3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.0.dylib; path = usr/lib/libsqlite3.0.dylib; sourceTree = SDKROOT; }; C32F6E0F1607AC35004BA8A1 /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = ""; }; @@ -68,6 +83,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 7CA53AF81C77495200420814 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; C395D505159E56760041510C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -82,6 +104,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7CA53AFC1C77495200420814 /* queueTests */ = { + isa = PBXGroup; + children = ( + 7CA53B051C77496800420814 /* EDQueueTests.m */, + 7CA53AFF1C77495200420814 /* Info.plist */, + ); + path = queueTests; + sourceTree = ""; + }; C32F6E05160793FB004BA8A1 /* EDQueue */ = { isa = PBXGroup; children = ( @@ -91,6 +122,7 @@ C32F6E231607E1FB004BA8A1 /* EDQueueStorageEngine.m */, 7CA53AF21C75F88E00420814 /* EDQueueJob.h */, 7CA53AF31C75F88E00420814 /* EDQueueJob.m */, + 7CA53B071C774EE900420814 /* EDQueuePersistentStorageProtocol.h */, ); name = EDQueue; sourceTree = ""; @@ -130,6 +162,7 @@ C32F6E05160793FB004BA8A1 /* EDQueue */, C32F6E0D1607AC13004BA8A1 /* Lib */, C395D512159E56760041510C /* Example */, + 7CA53AFC1C77495200420814 /* queueTests */, C395D50B159E56760041510C /* Frameworks */, C395D509159E56760041510C /* Products */, ); @@ -139,6 +172,7 @@ isa = PBXGroup; children = ( C395D508159E56760041510C /* queue.app */, + 7CA53AFB1C77495200420814 /* queueTests.xctest */, ); name = Products; sourceTree = ""; @@ -182,6 +216,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 7CA53AFA1C77495200420814 /* queueTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7CA53B021C77495200420814 /* Build configuration list for PBXNativeTarget "queueTests" */; + buildPhases = ( + 7CA53AF71C77495200420814 /* Sources */, + 7CA53AF81C77495200420814 /* Frameworks */, + 7CA53AF91C77495200420814 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 7CA53B011C77495200420814 /* PBXTargetDependency */, + ); + name = queueTests; + productName = queueTests; + productReference = 7CA53AFB1C77495200420814 /* queueTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; C395D507159E56760041510C /* queue */ = { isa = PBXNativeTarget; buildConfigurationList = C395D526159E56760041510C /* Build configuration list for PBXNativeTarget "queue" */; @@ -208,6 +260,12 @@ CLASSPREFIX = ED; LastUpgradeCheck = 0720; ORGANIZATIONNAME = "DIY, Co."; + TargetAttributes = { + 7CA53AFA1C77495200420814 = { + CreatedOnToolsVersion = 7.2.1; + TestTargetID = C395D507159E56760041510C; + }; + }; }; buildConfigurationList = C395D502159E56760041510C /* Build configuration list for PBXProject "queue" */; compatibilityVersion = "Xcode 3.2"; @@ -222,11 +280,19 @@ projectRoot = ""; targets = ( C395D507159E56760041510C /* queue */, + 7CA53AFA1C77495200420814 /* queueTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 7CA53AF91C77495200420814 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; C395D506159E56760041510C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -243,6 +309,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 7CA53AF71C77495200420814 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7CA53B061C77496800420814 /* EDQueueTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C395D504159E56760041510C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -263,6 +337,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 7CA53B011C77495200420814 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C395D507159E56760041510C /* queue */; + targetProxy = 7CA53B001C77495200420814 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ C395D515159E56760041510C /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -283,6 +365,52 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 7CA53B031C77495200420814 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = queueTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = oleg.shanyuk.queueTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/queue.app/queue"; + }; + name = Debug; + }; + 7CA53B041C77495200420814 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = queueTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = oleg.shanyuk.queueTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/queue.app/queue"; + }; + name = Release; + }; C395D524159E56760041510C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -385,6 +513,14 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 7CA53B021C77495200420814 /* Build configuration list for PBXNativeTarget "queueTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7CA53B031C77495200420814 /* Debug */, + 7CA53B041C77495200420814 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; C395D502159E56760041510C /* Build configuration list for PBXProject "queue" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Project/queue/EDAppDelegate.h b/Project/queue/EDAppDelegate.h index 3fdc149..da2a99a 100644 --- a/Project/queue/EDAppDelegate.h +++ b/Project/queue/EDAppDelegate.h @@ -6,14 +6,16 @@ // Copyright (c) 2012 Andrew Sliwinski. All rights reserved. // -#import +@import UIKit; + #import "EDQueue.h" @class EDViewController; @interface EDAppDelegate : UIResponder -@property (strong, nonatomic) UIWindow *window; -@property (strong, nonatomic) EDViewController *viewController; +@property (nonatomic) UIWindow *window; +@property (nonatomic) EDViewController *viewController; +@property (nonatomic) EDQueue *persistentTaskQueue; @end \ No newline at end of file diff --git a/Project/queue/EDAppDelegate.m b/Project/queue/EDAppDelegate.m index 9463890..9616f60 100644 --- a/Project/queue/EDAppDelegate.m +++ b/Project/queue/EDAppDelegate.m @@ -8,17 +8,22 @@ #import "EDAppDelegate.h" #import "EDViewController.h" +#import "EDQueueStorageEngine.h" @implementation EDAppDelegate -@synthesize window = _window; -@synthesize viewController = _viewController; - - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:@"database.sample.sqlite"]; + + self.persistentTaskQueue = [[EDQueue alloc] initWithPersistentStore:fmdbBasedStorage]; + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.viewController = [[EDViewController alloc] initWithNibName:@"EDViewController" bundle:nil]; + + self.viewController.persistentTaskQueue = self.persistentTaskQueue; + self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; return YES; @@ -28,13 +33,13 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( - (void)applicationDidBecomeActive:(UIApplication *)application { - [[EDQueue sharedInstance] setDelegate:self]; - [[EDQueue sharedInstance] start]; + [self.persistentTaskQueue setDelegate:self]; + [self.persistentTaskQueue start]; } - (void)applicationWillResignActive:(UIApplication *)application { - [[EDQueue sharedInstance] stop]; + [self.persistentTaskQueue stop]; } - (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(void (^)(EDQueueResult))block diff --git a/Project/queue/EDViewController.h b/Project/queue/EDViewController.h index a946793..4b1f1fe 100644 --- a/Project/queue/EDViewController.h +++ b/Project/queue/EDViewController.h @@ -11,7 +11,9 @@ @interface EDViewController : UIViewController -@property (nonatomic, retain) IBOutlet UITextView *activity; +@property (nonatomic) EDQueue *persistentTaskQueue; + +@property (nonatomic) IBOutlet UITextView *activity; - (IBAction)addSuccess:(id)sender; - (IBAction)addFail:(id)sender; diff --git a/Project/queue/EDViewController.m b/Project/queue/EDViewController.m index 233d12e..1233e19 100644 --- a/Project/queue/EDViewController.m +++ b/Project/queue/EDViewController.m @@ -37,19 +37,19 @@ - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interface - (IBAction)addSuccess:(id)sender { EDQueueJob *success = [[EDQueueJob alloc] initWithTask:@"success" userInfo:@{ @"nyan" : @"cat" }]; - [[EDQueue sharedInstance] enqueueJob:success]; + [self.persistentTaskQueue enqueueJob:success]; } - (IBAction)addFail:(id)sender { - EDQueueJob *success = [[EDQueueJob alloc] initWithTask:@"fail" userInfo:nil]; - [[EDQueue sharedInstance] enqueueJob:success]; + EDQueueJob *fail = [[EDQueueJob alloc] initWithTask:@"fail" userInfo:nil]; + [self.persistentTaskQueue enqueueJob:fail]; } - (IBAction)addCritical:(id)sender { - EDQueueJob *success = [[EDQueueJob alloc] initWithTask:@"critical" userInfo:nil]; - [[EDQueue sharedInstance] enqueueJob:success]; + EDQueueJob *critical = [[EDQueueJob alloc] initWithTask:@"critical" userInfo:nil]; + [self.persistentTaskQueue enqueueJob:critical]; } #pragma mark - Notifications diff --git a/Project/queueTests/EDQueueTests.m b/Project/queueTests/EDQueueTests.m new file mode 100644 index 0000000..a4d179c --- /dev/null +++ b/Project/queueTests/EDQueueTests.m @@ -0,0 +1,156 @@ +// +// EDQueueTests.m +// queue +// +// Created by Oleg Shanyuk on 19/02/16. +// Copyright © 2016 DIY, Co. All rights reserved. +// + +#import +#import "EDQueue.h" +#import "EDQueueStorageEngine.h" + +NSString *const EQTestDatabaseName = @"database.test.sqlite"; + +@interface EDQueueTests : XCTestCase +@property (nonatomic) EDQueue *queue; +@property (nonatomic) XCTestExpectation *currentExpectation; +@end + +@implementation EDQueueTests + +-(void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(EDQueueCompletionBlock)block +{ + [self.currentExpectation fulfill]; + + block(EDQueueResultSuccess); +} + +- (void)setUp { + EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:EQTestDatabaseName]; + + self.queue = [[EDQueue alloc] initWithPersistentStore:fmdbBasedStorage]; + + self.currentExpectation = nil; +} + +- (void)tearDown +{ + self.queue = nil; + + self.currentExpectation = nil; + + [EDQueueStorageEngine deleteDatabaseName:EQTestDatabaseName]; +} + +- (void)testQueueStart +{ + [self.queue start]; + + XCTAssertTrue(self.queue.isRunning); +} + +- (void)testQueueStop +{ + [self.queue start]; + [self.queue stop]; + + XCTAssertFalse(self.queue.isRunning); +} + +- (void)testQueueStartThenAddJob +{ + EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{}]; + + self.queue.delegate = self; + + self.currentExpectation = [self expectationWithDescription:@"queue should start soon"]; + + [self.queue start]; + + [self.queue enqueueJob:job]; + + [self waitForExpectationsWithTimeout:0.5 handler:nil]; +} + +- (void)testQueueAddJobThenStart +{ + EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{}]; + + self.queue.delegate = self; + + self.currentExpectation = [self expectationWithDescription:@"queue should start soon"]; + + [self.queue enqueueJob:job]; + + [self.queue start]; + + [self waitForExpectationsWithTimeout:0.5 handler:nil]; +} + +- (void)testJobExistsForTaskAndEmpty +{ + EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{}]; + + [self.queue enqueueJob:job]; + + XCTAssertTrue([self.queue jobExistsForTask:@"testTask"]); + + [self.queue empty]; + + XCTAssertFalse([self.queue jobExistsForTask:@"testTask"]); +} + +- (void)testJobDoesNotExistForTask +{ + EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{}]; + + [self.queue enqueueJob:job]; + + XCTAssertFalse([self.queue jobExistsForTask:@"testTaskFalse"]); +} + + +- (void)testJobIsActiveForTask +{ + EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{}]; + + [self.queue enqueueJob:job]; + + XCTAssertFalse([self.queue jobIsActiveForTask:@"testTask"]); + + [self.queue start]; + + sleep(1); + + XCTAssertTrue([self.queue jobIsActiveForTask:@"testTask"]); +} + +-(void)testNextJobForTask +{ + EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{@"testId":@"uniqueForThisTest"}]; + + [self.queue enqueueJob:job]; + + EDQueueJob *nextJob = [self.queue nextJobForTask:@"testTask"]; + + XCTAssertNotNil(nextJob); + + XCTAssertEqualObjects(nextJob.userInfo[@"testId"], @"uniqueForThisTest"); +} + +- (void)testIfQueuePersists +{ + EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTaskUniqueName" userInfo:@{@"test":@"test"}]; + + [self.queue enqueueJob:job]; + + self.queue = nil; + + [self setUp]; + + XCTAssertTrue([self.queue jobExistsForTask:@"testTaskUniqueName"]); +} + + +@end diff --git a/Project/queueTests/Info.plist b/Project/queueTests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/Project/queueTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/README.md b/README.md index 6c2d0f1..65f3bb2 100644 --- a/README.md +++ b/README.md @@ -19,52 +19,65 @@ YourAppDelegate.h YourAppDelegate.m ```objective-c +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:@"database.sample.sqlite"]; + + self.persistentTaskQueue = [[EDQueue alloc] initWithPersistentStore:fmdbBasedStorage]; + + /* There you go ^^ */ + + return YES; +} + +// + - (void)applicationDidBecomeActive:(UIApplication *)application { - [[EDQueue sharedInstance] setDelegate:self]; - [[EDQueue sharedInstance] start]; + [self.persistentTaskQueue setDelegate:self]; + [self.persistentTaskQueue start]; } - (void)applicationWillResignActive:(UIApplication *)application { - [[EDQueue sharedInstance] stop]; + [self.persistentTaskQueue stop]; } -- (EDQueueResult)queue:(EDQueue *)queue processJob:(NSDictionary *)job +- (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(void (^)(EDQueueResult))block { - sleep(1); // This won't block the main thread. Yay! + sleep(1); - // Wrap your job processing in a try-catch. Always use protection! @try { - if ([[job objectForKey:@"task"] isEqualToString:@"success"]) { - return EDQueueResultSuccess; - } else if ([[job objectForKey:@"task"] isEqualToString:@"fail"]) { - return EDQueueResultFail; + if ([job.task isEqualToString:@"success"]) { + block(EDQueueResultSuccess); + } else if ([job.task isEqualToString:@"fail"]) { + block(EDQueueResultFail); + } else { + block(EDQueueResultCritical); } } @catch (NSException *exception) { - return EDQueueResultCritical; + block(EDQueueResultCritical); } - - return EDQueueResultCritical; } ``` SomewhereElse.m ```objective-c -[[EDQueue sharedInstance] enqueueWithData:@{ @"foo" : @"bar" } forTask:@"nyancat"]; +[self.queue enqueueWithData:@{ @"foo" : @"bar" } forTask:@"nyancat"]; ``` -In order to keep things simple, the delegate method expects a return type of `EDQueueResult` which permits three distinct states: +Explanation to type of `EDQueueResult` which permits three distinct states: - `EDQueueResultSuccess`: Used to indicate that a job has completed successfully - `EDQueueResultFail`: Used to indicate that a job has failed and should be retried (up to the specified `retryLimit`) - `EDQueueResultCritical`: Used to indicate that a job has failed critically and should not be attempted again ### Handling Async Jobs -As of v0.6.0 queue includes a delegate method suited for handling asyncronous jobs such as HTTP requests or [Disk I/O](https://github.com/thisandagain/storage): +As of v0.7.3 queue switched to delegate method suited for handling asyncronous jobs such as HTTP requests or [Disk I/O](https://github.com/thisandagain/storage): + ```objective-c -- (void)queue:(EDQueue *)queue processJob:(NSDictionary *)job completion:(void (^)(EDQueueResult))block +- (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(void (^)(EDQueueResult))block { sleep(1); @@ -88,14 +101,14 @@ As of v0.7.0 queue includes a collection of methods to aid in queue introspectio ```objective-c - (Boolean)jobExistsForTask:(NSString *)task; - (Boolean)jobIsActiveForTask:(NSString *)task; -- (NSDictionary *)nextJobForTask:(NSString *)task; +- (EDQueueJob *)nextJobForTask:(NSString *)task; ``` --- ### Methods ```objective-c -- (void)enqueueWithData:(id)data forTask:(NSString *)task; +- (void)enqueueJob:(EDQueueJob *)job; - (void)start; - (void)stop; @@ -103,13 +116,12 @@ As of v0.7.0 queue includes a collection of methods to aid in queue introspectio - (Boolean)jobExistsForTask:(NSString *)task; - (Boolean)jobIsActiveForTask:(NSString *)task; -- (NSDictionary *)nextJobForTask:(NSString *)task; +- (EDQueueJob *)nextJobForTask:(NSString *)task; ``` ### Delegate Methods ```objective-c -- (EDQueueResult)queue:(EDQueue *)queue processJob:(NSDictionary *)job; -- (void)queue:(EDQueue *)queue processJob:(NSDictionary *)job completion:(void (^)(EDQueueResult result))block; +- (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(void (^)(EDQueueResult result))block; ``` ### Result Types @@ -139,7 +151,10 @@ EDQueueJobDidFail --- ### iOS Support -EDQueue is designed for iOS 5 and up. +EDQueue is designed for iOS 7 and up. ### ARC EDQueue is built using ARC. If you are including EDQueue in a project that **does not** use [Automatic Reference Counting (ARC)](http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html), you will need to set the `-fobjc-arc` compiler flag on all of the EDQueue source files. To do this in Xcode, go to your active target and select the "Build Phases" tab. Now select all EDQueue source files, press Enter, insert `-fobjc-arc` and then "Done" to enable ARC for EDQueue. + +### Nullability +EDQueue is growing up to be used with Swift later. Therefore it's code is being modernized, and wrapped nicely with 'nullable' markers \ No newline at end of file From 63a2f1265e8d071af2bb747c652d31a43dc4a6ae Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Fri, 19 Feb 2016 17:31:37 +0100 Subject: [PATCH 04/14] added NSDictionary contents enforcements --- EDQueue/EDQueueJob.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EDQueue/EDQueueJob.h b/EDQueue/EDQueueJob.h index 0d226b0..1359e5f 100644 --- a/EDQueue/EDQueueJob.h +++ b/EDQueue/EDQueueJob.h @@ -13,20 +13,20 @@ NS_ASSUME_NONNULL_BEGIN @interface EDQueueJob : NSObject @property(nonatomic, readonly) NSString *task; -@property(nonatomic, readonly) NSDictionary *userInfo; +@property(nonatomic, readonly) NSDictionary, id> *userInfo; @property(nonatomic, readonly, nullable) NSNumber *jobID; @property(nonatomic, readonly, nullable) NSNumber *attempts; @property(nonatomic, readonly, nullable) NSString *timeStamp; - (instancetype)initWithTask:(NSString *)task - userInfo:(nullable NSDictionary *)userInfo + userInfo:(nullable NSDictionary, id> *)userInfo jobID:(nullable NSNumber *)jobID atempts:(nullable NSNumber *)attemps timeStamp:(nullable NSString *)timeStamp; - (instancetype)initWithTask:(NSString *)task - userInfo:(nullable NSDictionary *)userInfo; + userInfo:(nullable NSDictionary, id> *)userInfo; - (instancetype)init NS_UNAVAILABLE; From 9673f1ac8e325d9a0477cd43eadaaf065062b3d0 Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Tue, 23 Feb 2016 17:01:06 +0100 Subject: [PATCH 05/14] Refactoring part one: removing unnecessary for public use properties of EDQueueJob; introduced EDQueueStoredJob protocol (to be renamed) --- EDQueue/EDQueue.m | 32 +++++------ EDQueue/EDQueueJob.h | 12 +--- EDQueue/EDQueueJob.m | 24 ++------ EDQueue/EDQueuePersistentStorageProtocol.h | 20 +++++-- EDQueue/EDQueueStorageEngine.m | 64 +++++++++++++++++----- 5 files changed, 90 insertions(+), 62 deletions(-) diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index 2ae2f3f..05df3c0 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -1,4 +1,4 @@ -// + // // EDQueue.m // queue // @@ -92,8 +92,8 @@ - (BOOL)jobIsActiveForTask:(NSString *)task */ - (nullable EDQueueJob *)nextJobForTask:(NSString *)task { - EDQueueJob *nextJobForTask = [self.storage fetchNextJobForTask:task]; - return nextJobForTask; + id nextStoredJobForTask = [self.storage fetchNextJobForTask:task]; + return nextStoredJobForTask.job; } /** @@ -157,19 +157,19 @@ - (void)tick if (self.isRunning && !self.isActive && [self.storage jobCount] > 0) { // Start job _isActive = YES; - EDQueueJob *job = [self.storage fetchNextJob]; - self.activeTask = job.task; + id storedJob = [self.storage fetchNextJob]; + self.activeTask = storedJob.job.task; // Pass job to delegate - [self.delegate queue:self processJob:job completion:^(EDQueueResult result) { - [self processJob:job withResult:result]; + [self.delegate queue:self processJob:storedJob.job completion:^(EDQueueResult result) { + [self processJob:storedJob withResult:result]; self.activeTask = nil; }]; } }); } -- (void)processJob:(EDQueueJob*)job withResult:(EDQueueResult)result +- (void)processJob:(id)storedJob withResult:(EDQueueResult)result { // Check result switch (result) { @@ -177,36 +177,36 @@ - (void)processJob:(EDQueueJob*)job withResult:(EDQueueResult)result [self postNotificationOnMainThread:@{ EDQueueNameKey : EDQueueJobDidSucceed, - EDQueueDataKey : job + EDQueueDataKey : storedJob.job }]; - [self.storage removeJob:job]; + [self.storage removeJob:storedJob]; break; case EDQueueResultFail: [self postNotificationOnMainThread:@{ EDQueueNameKey : EDQueueJobDidFail, - EDQueueDataKey : job + EDQueueDataKey : storedJob.job }]; - NSUInteger currentAttempt = job.attempts.integerValue + 1; + NSUInteger currentAttempt = storedJob.attempts.integerValue + 1; if (currentAttempt < self.retryLimit) { - [self.storage incrementAttemptForJob:job]; + [self.storage incrementAttemptForJob:storedJob]; } else { - [self.storage removeJob:job]; + [self.storage removeJob:storedJob]; } break; case EDQueueResultCritical: [self postNotificationOnMainThread:@{ EDQueueNameKey : EDQueueJobDidFail, - EDQueueDataKey : job + EDQueueDataKey : storedJob.job }]; [self errorWithMessage:@"Critical error. Job canceled."]; - [self.storage removeJob:job]; + [self.storage removeJob:storedJob]; break; } diff --git a/EDQueue/EDQueueJob.h b/EDQueue/EDQueueJob.h index 1359e5f..f3501e5 100644 --- a/EDQueue/EDQueueJob.h +++ b/EDQueue/EDQueueJob.h @@ -10,21 +10,12 @@ NS_ASSUME_NONNULL_BEGIN + @interface EDQueueJob : NSObject @property(nonatomic, readonly) NSString *task; @property(nonatomic, readonly) NSDictionary, id> *userInfo; -@property(nonatomic, readonly, nullable) NSNumber *jobID; -@property(nonatomic, readonly, nullable) NSNumber *attempts; -@property(nonatomic, readonly, nullable) NSString *timeStamp; - -- (instancetype)initWithTask:(NSString *)task - userInfo:(nullable NSDictionary, id> *)userInfo - jobID:(nullable NSNumber *)jobID - atempts:(nullable NSNumber *)attemps - timeStamp:(nullable NSString *)timeStamp; - - (instancetype)initWithTask:(NSString *)task userInfo:(nullable NSDictionary, id> *)userInfo; @@ -32,4 +23,5 @@ NS_ASSUME_NONNULL_BEGIN @end + NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/EDQueue/EDQueueJob.m b/EDQueue/EDQueueJob.m index 02a3ee3..025f2ff 100644 --- a/EDQueue/EDQueueJob.m +++ b/EDQueue/EDQueueJob.m @@ -14,27 +14,15 @@ @implementation EDQueueJob - (instancetype)initWithTask:(NSString *)task userInfo:(nullable NSDictionary *)userInfo - jobID:(nullable NSNumber *)jobID - atempts:(nullable NSNumber *)attemps - timeStamp:(nullable NSString *)timeStamp { - self = [super init]; + self = [super init]; - if (self) { - _jobID = [jobID copy]; - _task = [task copy]; - _userInfo = userInfo ? [userInfo copy] : @{}; - _attempts = [attemps copy]; - _timeStamp = [timeStamp copy]; - } + if (self) { + _task = [task copy]; + _userInfo = userInfo ? [userInfo copy] : @{}; + } - return self; -} - -- (instancetype)initWithTask:(NSString *)task - userInfo:(nullable NSDictionary *)userInfo -{ - return [self initWithTask:task userInfo:userInfo jobID:nil atempts:nil timeStamp:nil]; + return self; } @end diff --git a/EDQueue/EDQueuePersistentStorageProtocol.h b/EDQueue/EDQueuePersistentStorageProtocol.h index f62998e..11d39bf 100644 --- a/EDQueue/EDQueuePersistentStorageProtocol.h +++ b/EDQueue/EDQueuePersistentStorageProtocol.h @@ -12,18 +12,30 @@ NS_ASSUME_NONNULL_BEGIN @class EDQueueJob; + +@protocol EDQueueStoredJob + +@property(nonatomic, readonly) EDQueueJob *job; + +@property(nonatomic, readonly, nullable) NSNumber *jobID; +@property(nonatomic, readonly, nullable) NSNumber *attempts; +@property(nonatomic, readonly, nullable) NSString *timeStamp; + +@end + + @protocol EDQueuePersistentStorage - (void)createJob:(EDQueueJob *)job; - (BOOL)jobExistsForTask:(NSString *)task; -- (void)incrementAttemptForJob:(EDQueueJob *)jid; +- (void)incrementAttemptForJob:(id)jid; -- (void)removeJob:(EDQueueJob *)jid; +- (void)removeJob:(id)jid; - (void)removeAllJobs; - (NSUInteger)jobCount; -- (nullable EDQueueJob *)fetchNextJob; -- (nullable EDQueueJob *)fetchNextJobForTask:(NSString *)task; +- (nullable id)fetchNextJob; +- (nullable id)fetchNextJobForTask:(NSString *)task; @end diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index 442437f..5504490 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -26,6 +26,44 @@ return path; } +@interface EDQueueStorageEngineJob : NSObject + +- (instancetype)initWithTask:(NSString *)task + userInfo:(nullable NSDictionary, id> *)userInfo + jobID:(nullable NSNumber *)jobID + atempts:(nullable NSNumber *)attemps + timeStamp:(nullable NSString *)timeStamp; + +@end + +@implementation EDQueueStorageEngineJob + +@synthesize job = _job; +@synthesize jobID = _jobID; +@synthesize attempts = _attempts; +@synthesize timeStamp = _timeStamp; + +- (instancetype)initWithTask:(NSString *)task + userInfo:(nullable NSDictionary, id> *)userInfo + jobID:(nullable NSNumber *)jobID + atempts:(nullable NSNumber *)attemps + timeStamp:(nullable NSString *)timeStamp +{ + self = [super init]; + + if (self) { + + _job = [[EDQueueJob alloc] initWithTask:task userInfo:userInfo]; + _jobID = [jobID copy]; + _attempts = [attemps copy]; + _timeStamp = [timeStamp copy]; + } + + return self; +} + +@end + @interface EDQueueStorageEngine() @property (retain) FMDatabaseQueue *queue; @@ -127,7 +165,7 @@ - (BOOL)jobExistsForTask:(NSString *)task * * @return {void} */ -- (void)incrementAttemptForJob:(EDQueueJob *)job +- (void)incrementAttemptForJob:(id)job { if (!job.jobID) { return; @@ -146,7 +184,7 @@ - (void)incrementAttemptForJob:(EDQueueJob *)job * * @return {void} */ -- (void)removeJob:(EDQueueJob *)job +- (void)removeJob:(id)job { if (!job.jobID) { return; @@ -199,9 +237,9 @@ - (NSUInteger)jobCount * * @return {NSDictionary} */ -- (nullable EDQueueJob *)fetchNextJob +- (nullable id)fetchNextJob { - __block EDQueueJob *job; + __block id job; [self.queue inDatabase:^(FMDatabase *db) { FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue ORDER BY id ASC LIMIT 1"]; @@ -224,9 +262,9 @@ - (nullable EDQueueJob *)fetchNextJob * * @return {NSDictionary} */ -- (nullable EDQueueJob *)fetchNextJobForTask:(NSString *)task +- (nullable id)fetchNextJobForTask:(NSString *)task { - __block EDQueueJob *job; + __block id job; [self.queue inDatabase:^(FMDatabase *db) { FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE task = ? ORDER BY id ASC LIMIT 1", task]; @@ -244,17 +282,15 @@ - (nullable EDQueueJob *)fetchNextJobForTask:(NSString *)task #pragma mark - Private methods -- (EDQueueJob *)_jobFromResultSet:(FMResultSet *)rs +- (id)_jobFromResultSet:(FMResultSet *)rs { NSDictionary *userInfo = [NSJSONSerialization JSONObjectWithData:[[rs stringForColumn:@"data"] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil]; - EDQueueJob *job = [[EDQueueJob alloc] initWithTask:[rs stringForColumn:@"task"] - userInfo:userInfo - jobID:@([rs intForColumn:@"id"]) - atempts:@([rs intForColumn:@"attempts"]) - timeStamp:[rs stringForColumn:@"stamp"]]; - - + EDQueueStorageEngineJob *job = [[EDQueueStorageEngineJob alloc] initWithTask:[rs stringForColumn:@"task"] + userInfo:userInfo + jobID:@([rs intForColumn:@"id"]) + atempts:@([rs intForColumn:@"attempts"]) + timeStamp:[rs stringForColumn:@"stamp"]]; return job; } From 1f2c92a8a1b79052fb2b05d219ba310c39f28bfa Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Tue, 23 Feb 2016 17:06:10 +0100 Subject: [PATCH 06/14] Renamed EDQueueStoredJob to EDQueueStorageItem which is way more logical --- EDQueue/EDQueue.m | 6 +++--- EDQueue/EDQueuePersistentStorageProtocol.h | 10 +++++----- EDQueue/EDQueueStorageEngine.m | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index 05df3c0..0d3b85e 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -92,7 +92,7 @@ - (BOOL)jobIsActiveForTask:(NSString *)task */ - (nullable EDQueueJob *)nextJobForTask:(NSString *)task { - id nextStoredJobForTask = [self.storage fetchNextJobForTask:task]; + id nextStoredJobForTask = [self.storage fetchNextJobForTask:task]; return nextStoredJobForTask.job; } @@ -157,7 +157,7 @@ - (void)tick if (self.isRunning && !self.isActive && [self.storage jobCount] > 0) { // Start job _isActive = YES; - id storedJob = [self.storage fetchNextJob]; + id storedJob = [self.storage fetchNextJob]; self.activeTask = storedJob.job.task; // Pass job to delegate @@ -169,7 +169,7 @@ - (void)tick }); } -- (void)processJob:(id)storedJob withResult:(EDQueueResult)result +- (void)processJob:(id)storedJob withResult:(EDQueueResult)result { // Check result switch (result) { diff --git a/EDQueue/EDQueuePersistentStorageProtocol.h b/EDQueue/EDQueuePersistentStorageProtocol.h index 11d39bf..275cd6b 100644 --- a/EDQueue/EDQueuePersistentStorageProtocol.h +++ b/EDQueue/EDQueuePersistentStorageProtocol.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @class EDQueueJob; -@protocol EDQueueStoredJob +@protocol EDQueueStorageItem @property(nonatomic, readonly) EDQueueJob *job; @@ -28,14 +28,14 @@ NS_ASSUME_NONNULL_BEGIN - (void)createJob:(EDQueueJob *)job; - (BOOL)jobExistsForTask:(NSString *)task; -- (void)incrementAttemptForJob:(id)jid; +- (void)incrementAttemptForJob:(id)jid; -- (void)removeJob:(id)jid; +- (void)removeJob:(id)jid; - (void)removeAllJobs; - (NSUInteger)jobCount; -- (nullable id)fetchNextJob; -- (nullable id)fetchNextJobForTask:(NSString *)task; +- (nullable id)fetchNextJob; +- (nullable id)fetchNextJobForTask:(NSString *)task; @end diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index 5504490..365f9c4 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -26,7 +26,7 @@ return path; } -@interface EDQueueStorageEngineJob : NSObject +@interface EDQueueStorageEngineJob : NSObject - (instancetype)initWithTask:(NSString *)task userInfo:(nullable NSDictionary, id> *)userInfo @@ -165,7 +165,7 @@ - (BOOL)jobExistsForTask:(NSString *)task * * @return {void} */ -- (void)incrementAttemptForJob:(id)job +- (void)incrementAttemptForJob:(id)job { if (!job.jobID) { return; @@ -184,7 +184,7 @@ - (void)incrementAttemptForJob:(id)job * * @return {void} */ -- (void)removeJob:(id)job +- (void)removeJob:(id)job { if (!job.jobID) { return; @@ -237,9 +237,9 @@ - (NSUInteger)jobCount * * @return {NSDictionary} */ -- (nullable id)fetchNextJob +- (nullable id)fetchNextJob { - __block id job; + __block id job; [self.queue inDatabase:^(FMDatabase *db) { FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue ORDER BY id ASC LIMIT 1"]; @@ -262,9 +262,9 @@ - (NSUInteger)jobCount * * @return {NSDictionary} */ -- (nullable id)fetchNextJobForTask:(NSString *)task +- (nullable id)fetchNextJobForTask:(NSString *)task { - __block id job; + __block id job; [self.queue inDatabase:^(FMDatabase *db) { FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE task = ? ORDER BY id ASC LIMIT 1", task]; @@ -282,7 +282,7 @@ - (NSUInteger)jobCount #pragma mark - Private methods -- (id)_jobFromResultSet:(FMResultSet *)rs +- (id)_jobFromResultSet:(FMResultSet *)rs { NSDictionary *userInfo = [NSJSONSerialization JSONObjectWithData:[[rs stringForColumn:@"data"] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil]; From bd34c482b1539147aec1adb8aca98ed07dd4fbd5 Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Tue, 23 Feb 2016 17:23:04 +0100 Subject: [PATCH 07/14] Refeactor stage two: code Task becomes Tag --- EDQueue/EDQueue.h | 6 +-- EDQueue/EDQueue.m | 18 ++++----- EDQueue/EDQueueJob.h | 6 +-- EDQueue/EDQueueJob.m | 6 +-- EDQueue/EDQueuePersistentStorageProtocol.h | 4 +- EDQueue/EDQueueStorageEngine.m | 44 +++++++++++----------- Project/queue/EDAppDelegate.m | 4 +- Project/queue/EDViewController.m | 6 +-- Project/queueTests/EDQueueTests.m | 28 +++++++------- 9 files changed, 61 insertions(+), 61 deletions(-) diff --git a/EDQueue/EDQueue.h b/EDQueue/EDQueue.h index a8a986c..ef20d36 100755 --- a/EDQueue/EDQueue.h +++ b/EDQueue/EDQueue.h @@ -58,9 +58,9 @@ extern NSString *const EDQueueDidDrain; - (void)stop; - (void)empty; -- (BOOL)jobExistsForTask:(NSString *)task; -- (BOOL)jobIsActiveForTask:(NSString *)task; -- (nullable EDQueueJob *)nextJobForTask:(NSString *)task; +- (BOOL)jobExistsForTag:(NSString *)tag; +- (BOOL)jobIsActiveForTag:(NSString *)tag; +- (nullable EDQueueJob *)nextJobForTag:(NSString *)tag; @end diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index 0d3b85e..8dd2773 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -23,7 +23,7 @@ @interface EDQueue () -@property (nonatomic, readwrite, nullable) NSString *activeTask; +@property (nonatomic, readwrite, nullable) NSString *activeTaskTag; @end @@ -64,9 +64,9 @@ - (void)enqueueJob:(EDQueueJob *)job * * @return {Boolean} */ -- (BOOL)jobExistsForTask:(NSString *)task +- (BOOL)jobExistsForTag:(NSString *)tag { - BOOL jobExists = [self.storage jobExistsForTask:task]; + BOOL jobExists = [self.storage jobExistsForTag:tag]; return jobExists; } @@ -77,9 +77,9 @@ - (BOOL)jobExistsForTask:(NSString *)task * * @return {Boolean} */ -- (BOOL)jobIsActiveForTask:(NSString *)task +- (BOOL)jobIsActiveForTag:(NSString *)tag { - BOOL jobIsActive = [self.activeTask length] > 0 && [self.activeTask isEqualToString:task]; + BOOL jobIsActive = [self.activeTaskTag length] > 0 && [self.activeTaskTag isEqualToString:tag]; return jobIsActive; } @@ -90,9 +90,9 @@ - (BOOL)jobIsActiveForTask:(NSString *)task * * @return {NSArray} */ -- (nullable EDQueueJob *)nextJobForTask:(NSString *)task +- (nullable EDQueueJob *)nextJobForTag:(NSString *)tag { - id nextStoredJobForTask = [self.storage fetchNextJobForTask:task]; + id nextStoredJobForTask = [self.storage fetchNextJobForTag:tag]; return nextStoredJobForTask.job; } @@ -158,12 +158,12 @@ - (void)tick // Start job _isActive = YES; id storedJob = [self.storage fetchNextJob]; - self.activeTask = storedJob.job.task; + self.activeTaskTag = storedJob.job.tag; // Pass job to delegate [self.delegate queue:self processJob:storedJob.job completion:^(EDQueueResult result) { [self processJob:storedJob withResult:result]; - self.activeTask = nil; + self.activeTaskTag = nil; }]; } }); diff --git a/EDQueue/EDQueueJob.h b/EDQueue/EDQueueJob.h index f3501e5..05bb185 100644 --- a/EDQueue/EDQueueJob.h +++ b/EDQueue/EDQueueJob.h @@ -13,11 +13,11 @@ NS_ASSUME_NONNULL_BEGIN @interface EDQueueJob : NSObject -@property(nonatomic, readonly) NSString *task; +@property(nonatomic, readonly) NSString *tag; @property(nonatomic, readonly) NSDictionary, id> *userInfo; -- (instancetype)initWithTask:(NSString *)task - userInfo:(nullable NSDictionary, id> *)userInfo; +- (instancetype)initWithTag:(NSString *)tag + userInfo:(nullable NSDictionary, id> *)userInfo; - (instancetype)init NS_UNAVAILABLE; diff --git a/EDQueue/EDQueueJob.m b/EDQueue/EDQueueJob.m index 025f2ff..14e0e6a 100644 --- a/EDQueue/EDQueueJob.m +++ b/EDQueue/EDQueueJob.m @@ -12,13 +12,13 @@ @implementation EDQueueJob -- (instancetype)initWithTask:(NSString *)task - userInfo:(nullable NSDictionary *)userInfo +- (instancetype)initWithTag:(NSString *)tag + userInfo:(nullable NSDictionary, id> *)userInfo { self = [super init]; if (self) { - _task = [task copy]; + _tag = [tag copy]; _userInfo = userInfo ? [userInfo copy] : @{}; } diff --git a/EDQueue/EDQueuePersistentStorageProtocol.h b/EDQueue/EDQueuePersistentStorageProtocol.h index 275cd6b..d575d0f 100644 --- a/EDQueue/EDQueuePersistentStorageProtocol.h +++ b/EDQueue/EDQueuePersistentStorageProtocol.h @@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol EDQueuePersistentStorage - (void)createJob:(EDQueueJob *)job; -- (BOOL)jobExistsForTask:(NSString *)task; +- (BOOL)jobExistsForTag:(NSString *)tag; - (void)incrementAttemptForJob:(id)jid; - (void)removeJob:(id)jid; @@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSUInteger)jobCount; - (nullable id)fetchNextJob; -- (nullable id)fetchNextJobForTask:(NSString *)task; +- (nullable id)fetchNextJobForTag:(NSString *)tag; @end diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index 365f9c4..39e2a96 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -28,11 +28,11 @@ @interface EDQueueStorageEngineJob : NSObject -- (instancetype)initWithTask:(NSString *)task - userInfo:(nullable NSDictionary, id> *)userInfo - jobID:(nullable NSNumber *)jobID - atempts:(nullable NSNumber *)attemps - timeStamp:(nullable NSString *)timeStamp; +- (instancetype)initWithTag:(NSString *)tag + userInfo:(nullable NSDictionary, id> *)userInfo + jobID:(nullable NSNumber *)jobID + atempts:(nullable NSNumber *)attemps + timeStamp:(nullable NSString *)timeStamp; @end @@ -43,17 +43,17 @@ @implementation EDQueueStorageEngineJob @synthesize attempts = _attempts; @synthesize timeStamp = _timeStamp; -- (instancetype)initWithTask:(NSString *)task - userInfo:(nullable NSDictionary, id> *)userInfo - jobID:(nullable NSNumber *)jobID - atempts:(nullable NSNumber *)attemps - timeStamp:(nullable NSString *)timeStamp +- (instancetype)initWithTag:(NSString *)tag + userInfo:(nullable NSDictionary, id> *)userInfo + jobID:(nullable NSNumber *)jobID + atempts:(nullable NSNumber *)attemps + timeStamp:(nullable NSString *)timeStamp { self = [super init]; if (self) { - _job = [[EDQueueJob alloc] initWithTask:task userInfo:userInfo]; + _job = [[EDQueueJob alloc] initWithTag:tag userInfo:userInfo]; _jobID = [jobID copy]; _attempts = [attemps copy]; _timeStamp = [timeStamp copy]; @@ -92,7 +92,7 @@ - (nullable instancetype)initWithName:(NSString *)name } [self.queue inDatabase:^(FMDatabase *db) { - [db executeUpdate:@"CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY, task TEXT NOT NULL, data TEXT NOT NULL, attempts INTEGER DEFAULT 0, stamp STRING DEFAULT (strftime('%s','now')) NOT NULL, udef_1 TEXT, udef_2 TEXT)"]; + [db executeUpdate:@"CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY, tag TEXT NOT NULL, data TEXT NOT NULL, attempts INTEGER DEFAULT 0, stamp STRING DEFAULT (strftime('%s','now')) NOT NULL, udef_1 TEXT, udef_2 TEXT)"]; [self _databaseHadError:[db hadError] fromDatabase:db]; }]; } @@ -128,7 +128,7 @@ - (void)createJob:(EDQueueJob *)job } [self.queue inDatabase:^(FMDatabase *db) { - [db executeUpdate:@"INSERT INTO queue (task, data) VALUES (?, ?)", job.task, dataString]; + [db executeUpdate:@"INSERT INTO queue (tag, data) VALUES (?, ?)", job.tag, dataString]; [self _databaseHadError:[db hadError] fromDatabase:db]; }]; } @@ -140,12 +140,12 @@ - (void)createJob:(EDQueueJob *)job * * @return {BOOL} */ -- (BOOL)jobExistsForTask:(NSString *)task +- (BOOL)jobExistsForTag:(NSString *)tag { __block BOOL jobExists = NO; [self.queue inDatabase:^(FMDatabase *db) { - FMResultSet *rs = [db executeQuery:@"SELECT count(id) AS count FROM queue WHERE task = ?", task]; + FMResultSet *rs = [db executeQuery:@"SELECT count(id) AS count FROM queue WHERE tag = ?", tag]; [self _databaseHadError:[db hadError] fromDatabase:db]; while ([rs next]) { @@ -262,12 +262,12 @@ - (NSUInteger)jobCount * * @return {NSDictionary} */ -- (nullable id)fetchNextJobForTask:(NSString *)task +- (nullable id)fetchNextJobForTag:(NSString *)tag { __block id job; [self.queue inDatabase:^(FMDatabase *db) { - FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE task = ? ORDER BY id ASC LIMIT 1", task]; + FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE tag = ? ORDER BY id ASC LIMIT 1", tag]; [self _databaseHadError:[db hadError] fromDatabase:db]; while ([rs next]) { @@ -286,11 +286,11 @@ - (NSUInteger)jobCount { NSDictionary *userInfo = [NSJSONSerialization JSONObjectWithData:[[rs stringForColumn:@"data"] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil]; - EDQueueStorageEngineJob *job = [[EDQueueStorageEngineJob alloc] initWithTask:[rs stringForColumn:@"task"] - userInfo:userInfo - jobID:@([rs intForColumn:@"id"]) - atempts:@([rs intForColumn:@"attempts"]) - timeStamp:[rs stringForColumn:@"stamp"]]; + EDQueueStorageEngineJob *job = [[EDQueueStorageEngineJob alloc] initWithTag:[rs stringForColumn:@"tag"] + userInfo:userInfo + jobID:@([rs intForColumn:@"id"]) + atempts:@([rs intForColumn:@"attempts"]) + timeStamp:[rs stringForColumn:@"stamp"]]; return job; } diff --git a/Project/queue/EDAppDelegate.m b/Project/queue/EDAppDelegate.m index 9616f60..4ebd174 100644 --- a/Project/queue/EDAppDelegate.m +++ b/Project/queue/EDAppDelegate.m @@ -47,9 +47,9 @@ - (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(void (^) sleep(1); @try { - if ([job.task isEqualToString:@"success"]) { + if ([job.tag isEqualToString:@"success"]) { block(EDQueueResultSuccess); - } else if ([job.task isEqualToString:@"fail"]) { + } else if ([job.tag isEqualToString:@"fail"]) { block(EDQueueResultFail); } else { block(EDQueueResultCritical); diff --git a/Project/queue/EDViewController.m b/Project/queue/EDViewController.m index 1233e19..b39eb90 100644 --- a/Project/queue/EDViewController.m +++ b/Project/queue/EDViewController.m @@ -36,19 +36,19 @@ - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interface - (IBAction)addSuccess:(id)sender { - EDQueueJob *success = [[EDQueueJob alloc] initWithTask:@"success" userInfo:@{ @"nyan" : @"cat" }]; + EDQueueJob *success = [[EDQueueJob alloc] initWithTag:@"success" userInfo:@{ @"nyan" : @"cat" }]; [self.persistentTaskQueue enqueueJob:success]; } - (IBAction)addFail:(id)sender { - EDQueueJob *fail = [[EDQueueJob alloc] initWithTask:@"fail" userInfo:nil]; + EDQueueJob *fail = [[EDQueueJob alloc] initWithTag:@"fail" userInfo:nil]; [self.persistentTaskQueue enqueueJob:fail]; } - (IBAction)addCritical:(id)sender { - EDQueueJob *critical = [[EDQueueJob alloc] initWithTask:@"critical" userInfo:nil]; + EDQueueJob *critical = [[EDQueueJob alloc] initWithTag:@"critical" userInfo:nil]; [self.persistentTaskQueue enqueueJob:critical]; } diff --git a/Project/queueTests/EDQueueTests.m b/Project/queueTests/EDQueueTests.m index a4d179c..d3bb613 100644 --- a/Project/queueTests/EDQueueTests.m +++ b/Project/queueTests/EDQueueTests.m @@ -60,7 +60,7 @@ - (void)testQueueStop - (void)testQueueStartThenAddJob { - EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{}]; + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; self.queue.delegate = self; @@ -75,7 +75,7 @@ - (void)testQueueStartThenAddJob - (void)testQueueAddJobThenStart { - EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{}]; + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; self.queue.delegate = self; @@ -90,49 +90,49 @@ - (void)testQueueAddJobThenStart - (void)testJobExistsForTaskAndEmpty { - EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{}]; + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; [self.queue enqueueJob:job]; - XCTAssertTrue([self.queue jobExistsForTask:@"testTask"]); + XCTAssertTrue([self.queue jobExistsForTag:@"testTask"]); [self.queue empty]; - XCTAssertFalse([self.queue jobExistsForTask:@"testTask"]); + XCTAssertFalse([self.queue jobExistsForTag:@"testTask"]); } - (void)testJobDoesNotExistForTask { - EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{}]; + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; [self.queue enqueueJob:job]; - XCTAssertFalse([self.queue jobExistsForTask:@"testTaskFalse"]); + XCTAssertFalse([self.queue jobExistsForTag:@"testTaskFalse"]); } - (void)testJobIsActiveForTask { - EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{}]; + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; [self.queue enqueueJob:job]; - XCTAssertFalse([self.queue jobIsActiveForTask:@"testTask"]); + XCTAssertFalse([self.queue jobIsActiveForTag:@"testTask"]); [self.queue start]; sleep(1); - XCTAssertTrue([self.queue jobIsActiveForTask:@"testTask"]); + XCTAssertTrue([self.queue jobIsActiveForTag:@"testTask"]); } -(void)testNextJobForTask { - EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTask" userInfo:@{@"testId":@"uniqueForThisTest"}]; + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{@"testId":@"uniqueForThisTest"}]; [self.queue enqueueJob:job]; - EDQueueJob *nextJob = [self.queue nextJobForTask:@"testTask"]; + EDQueueJob *nextJob = [self.queue nextJobForTag:@"testTask"]; XCTAssertNotNil(nextJob); @@ -141,7 +141,7 @@ -(void)testNextJobForTask - (void)testIfQueuePersists { - EDQueueJob *job = [[EDQueueJob alloc] initWithTask:@"testTaskUniqueName" userInfo:@{@"test":@"test"}]; + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTaskUniqueName" userInfo:@{@"test":@"test"}]; [self.queue enqueueJob:job]; @@ -149,7 +149,7 @@ - (void)testIfQueuePersists [self setUp]; - XCTAssertTrue([self.queue jobExistsForTask:@"testTaskUniqueName"]); + XCTAssertTrue([self.queue jobExistsForTag:@"testTaskUniqueName"]); } From 068d114637fb754c97dbe47c7ba8da2beb87272d Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Wed, 24 Feb 2016 01:29:15 +0100 Subject: [PATCH 08/14] Refactoring: tune things up in line with merge request #22 --- EDQueue.podspec | 6 +-- EDQueue/EDQueue.h | 4 +- EDQueue/EDQueue.m | 41 ++++++++++------ EDQueue/EDQueueStorageEngine.m | 9 ++-- Project/queue.xcodeproj/project.pbxproj | 1 + Project/queue/EDAppDelegate.h | 1 - Project/queue/EDAppDelegate.m | 13 ++--- Project/queue/EDViewController.h | 2 - Project/queue/EDViewController.m | 6 +-- Project/queueTests/EDQueueTests.m | 8 ++-- README.md | 64 ++++++++++++------------- 11 files changed, 80 insertions(+), 75 deletions(-) diff --git a/EDQueue.podspec b/EDQueue.podspec index 21fe78a..d3950d3 100644 --- a/EDQueue.podspec +++ b/EDQueue.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |s| s.name = 'EDQueue' - s.version = '0.7.3' + s.version = '1.0' s.license = 'MIT' s.summary = 'A persistent background job queue for iOS.' - s.homepage = 'https://github.com/gelosi/queue' + s.homepage = 'https://github.com/thisandagain/queue' s.authors = {'Andrew Sliwinski' => 'andrewsliwinski@acm.org', 'Francois Lambert' => 'flambert@mirego.com', 'Oleg Shanyuk' => 'oleg.shanyuk@gmail.com'} - s.source = { :git => 'https://github.com/gelosi/queue.git', :tag => 'v0.7.3' } + s.source = { :git => 'https://github.com/thisandagain/queue.git', :tag => 'v1.0' } s.platform = :ios, '7.0' s.source_files = 'EDQueue' s.library = 'sqlite3.0' diff --git a/EDQueue/EDQueue.h b/EDQueue/EDQueue.h index ef20d36..05edf30 100755 --- a/EDQueue/EDQueue.h +++ b/EDQueue/EDQueue.h @@ -47,10 +47,12 @@ extern NSString *const EDQueueDidDrain; */ @property (nonatomic, readonly) BOOL isActive; /** - * Retry limit for failing tasks (will be elimitated and moved to Job later) + * Retry limit for failing jobs (will be elimitated and moved to Job later) */ @property (nonatomic) NSUInteger retryLimit; ++ (instancetype)defaultQueue; + - (instancetype)initWithPersistentStore:(id)persistentStore; - (void)enqueueJob:(EDQueueJob *)job; diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index 8dd2773..56c9359 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -23,13 +23,27 @@ @interface EDQueue () -@property (nonatomic, readwrite, nullable) NSString *activeTaskTag; +@property (nonatomic, readwrite, nullable) NSString *activeJobTag; @end @implementation EDQueue ++ (instancetype)defaultQueue +{ + static EDQueue *defaultQueue; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:@"edqueue.default.v7.3.sqlite"]; + + defaultQueue = [[EDQueue alloc] initWithPersistentStore:fmdbBasedStorage]; + }); + + return defaultQueue; +} + - (instancetype)initWithPersistentStore:(id)persistentStore { self = [super init]; @@ -46,8 +60,7 @@ - (instancetype)initWithPersistentStore:(id)persistent /** * Adds a new job to the queue. * - * @param {id} Data - * @param {NSString} Task label + * @param {EDQueueJob} job * * @return {void} */ @@ -58,9 +71,9 @@ - (void)enqueueJob:(EDQueueJob *)job } /** - * Returns true if a job exists for this task. + * Returns true if a job exists for this tag. * - * @param {NSString} Task label + * @param {NSString} job tag * * @return {Boolean} */ @@ -71,29 +84,29 @@ - (BOOL)jobExistsForTag:(NSString *)tag } /** - * Returns true if the active job if for this task. + * Returns true if the active job if for this tag. * - * @param {NSString} Task label + * @param {NSString} job tag * * @return {Boolean} */ - (BOOL)jobIsActiveForTag:(NSString *)tag { - BOOL jobIsActive = [self.activeTaskTag length] > 0 && [self.activeTaskTag isEqualToString:tag]; + BOOL jobIsActive = [self.activeJobTag length] > 0 && [self.activeJobTag isEqualToString:tag]; return jobIsActive; } /** - * Returns the list of jobs for this + * Returns the next job for tag * - * @param {NSString} Task label + * @param {NSString} job tag * * @return {NSArray} */ - (nullable EDQueueJob *)nextJobForTag:(NSString *)tag { - id nextStoredJobForTask = [self.storage fetchNextJobForTag:tag]; - return nextStoredJobForTask.job; + id item = [self.storage fetchNextJobForTag:tag]; + return item.job; } /** @@ -158,12 +171,12 @@ - (void)tick // Start job _isActive = YES; id storedJob = [self.storage fetchNextJob]; - self.activeTaskTag = storedJob.job.tag; + self.activeJobTag = storedJob.job.tag; // Pass job to delegate [self.delegate queue:self processJob:storedJob.job completion:^(EDQueueResult result) { [self processJob:storedJob withResult:result]; - self.activeTaskTag = nil; + self.activeJobTag = nil; }]; } }); diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index 39e2a96..1387314 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -114,7 +114,6 @@ - (void)dealloc * * @return {void} */ -//- (void)createJob:(id)data forTask:(id)task - (void)createJob:(EDQueueJob *)job { NSString *dataString = nil; @@ -134,9 +133,9 @@ - (void)createJob:(EDQueueJob *)job } /** - * Tells if a job exists for the specified task name. + * Tells if a job exists for the specified tag * - * @param {NSString} Task name + * @param {NSString} tag * * @return {BOOL} */ @@ -256,9 +255,9 @@ - (NSUInteger)jobCount } /** - * Returns the oldest job for the task from the datastore. + * Returns the oldest job for the with tag from the datastore. * - * @param {id} Task label + * @param {id} tag * * @return {NSDictionary} */ diff --git a/Project/queue.xcodeproj/project.pbxproj b/Project/queue.xcodeproj/project.pbxproj index d71b9ad..79c5a31 100644 --- a/Project/queue.xcodeproj/project.pbxproj +++ b/Project/queue.xcodeproj/project.pbxproj @@ -520,6 +520,7 @@ 7CA53B041C77495200420814 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; C395D502159E56760041510C /* Build configuration list for PBXProject "queue" */ = { isa = XCConfigurationList; diff --git a/Project/queue/EDAppDelegate.h b/Project/queue/EDAppDelegate.h index da2a99a..1f77188 100644 --- a/Project/queue/EDAppDelegate.h +++ b/Project/queue/EDAppDelegate.h @@ -16,6 +16,5 @@ @property (nonatomic) UIWindow *window; @property (nonatomic) EDViewController *viewController; -@property (nonatomic) EDQueue *persistentTaskQueue; @end \ No newline at end of file diff --git a/Project/queue/EDAppDelegate.m b/Project/queue/EDAppDelegate.m index 4ebd174..a7fe2fe 100644 --- a/Project/queue/EDAppDelegate.m +++ b/Project/queue/EDAppDelegate.m @@ -14,16 +14,9 @@ @implementation EDAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:@"database.sample.sqlite"]; - - self.persistentTaskQueue = [[EDQueue alloc] initWithPersistentStore:fmdbBasedStorage]; - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.viewController = [[EDViewController alloc] initWithNibName:@"EDViewController" bundle:nil]; - - self.viewController.persistentTaskQueue = self.persistentTaskQueue; - self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; return YES; @@ -33,13 +26,13 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( - (void)applicationDidBecomeActive:(UIApplication *)application { - [self.persistentTaskQueue setDelegate:self]; - [self.persistentTaskQueue start]; + [[EDQueue defaultQueue] setDelegate:self]; + [[EDQueue defaultQueue] start]; } - (void)applicationWillResignActive:(UIApplication *)application { - [self.persistentTaskQueue stop]; + [[EDQueue defaultQueue] stop]; } - (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(void (^)(EDQueueResult))block diff --git a/Project/queue/EDViewController.h b/Project/queue/EDViewController.h index 4b1f1fe..b95f5d3 100644 --- a/Project/queue/EDViewController.h +++ b/Project/queue/EDViewController.h @@ -11,8 +11,6 @@ @interface EDViewController : UIViewController -@property (nonatomic) EDQueue *persistentTaskQueue; - @property (nonatomic) IBOutlet UITextView *activity; - (IBAction)addSuccess:(id)sender; diff --git a/Project/queue/EDViewController.m b/Project/queue/EDViewController.m index b39eb90..1ab988d 100644 --- a/Project/queue/EDViewController.m +++ b/Project/queue/EDViewController.m @@ -37,19 +37,19 @@ - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interface - (IBAction)addSuccess:(id)sender { EDQueueJob *success = [[EDQueueJob alloc] initWithTag:@"success" userInfo:@{ @"nyan" : @"cat" }]; - [self.persistentTaskQueue enqueueJob:success]; + [[EDQueue defaultQueue] enqueueJob:success]; } - (IBAction)addFail:(id)sender { EDQueueJob *fail = [[EDQueueJob alloc] initWithTag:@"fail" userInfo:nil]; - [self.persistentTaskQueue enqueueJob:fail]; + [[EDQueue defaultQueue] enqueueJob:fail]; } - (IBAction)addCritical:(id)sender { EDQueueJob *critical = [[EDQueueJob alloc] initWithTag:@"critical" userInfo:nil]; - [self.persistentTaskQueue enqueueJob:critical]; + [[EDQueue defaultQueue] enqueueJob:critical]; } #pragma mark - Notifications diff --git a/Project/queueTests/EDQueueTests.m b/Project/queueTests/EDQueueTests.m index d3bb613..ca39ce8 100644 --- a/Project/queueTests/EDQueueTests.m +++ b/Project/queueTests/EDQueueTests.m @@ -88,7 +88,7 @@ - (void)testQueueAddJobThenStart [self waitForExpectationsWithTimeout:0.5 handler:nil]; } -- (void)testJobExistsForTaskAndEmpty +- (void)testJobExistsForTagAndEmpty { EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; @@ -101,7 +101,7 @@ - (void)testJobExistsForTaskAndEmpty XCTAssertFalse([self.queue jobExistsForTag:@"testTask"]); } -- (void)testJobDoesNotExistForTask +- (void)testJobDoesNotExistForTag { EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; @@ -111,7 +111,7 @@ - (void)testJobDoesNotExistForTask } -- (void)testJobIsActiveForTask +- (void)testJobIsActiveForTag { EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; @@ -126,7 +126,7 @@ - (void)testJobIsActiveForTask XCTAssertTrue([self.queue jobIsActiveForTag:@"testTask"]); } --(void)testNextJobForTask +-(void)testNextJobForTag { EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{@"testId":@"uniqueForThisTest"}]; diff --git a/README.md b/README.md index 65f3bb2..383b0ee 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,22 @@ While `NSOperation` and `NSOperationQueue` work well for some repetitive problem The easiest way to get going with EDQueue is to take a look at the included example application. The XCode project file can be found in `Project > queue.xcodeproj`. ### Setup -EDQueue needs both `libsqlite3.0.dylib` and [FMDB](https://github.com/ccgus/fmdb) for the storage engine. As always, the quickest way to take care of all those details is to use [CocoaPods](http://cocoapods.org/). EDQueue is implemented as a singleton as to allow jobs to be created from anywhere throughout an application. However, tasks are all processed through a single delegate method and thus it often makes the most sense to setup EDQueue within the application delegate: +EDQueue needs both `libsqlite3.0.dylib` and [FMDB](https://github.com/ccgus/fmdb) for the storage engine. As always, the quickest way to take care of all those details is to use [CocoaPods](http://cocoapods.org/). EDQueue is implemented as a singleton as to allow jobs to be created from anywhere throughout an application. However, tasks are all processed through a single delegate method and thus it often makes the most sense to setup EDQueue within the application delegate. See examples below. + +#### No-sigleton approach + +EDQueue is easy to use with your DI or other way around singletons. You also able to use non-FMDB storage for persistence. To do so have a look at `EDQueuePersistentStorageProtocol`. It's pretty straighforward. + +Here's exaple of configuring EDQueue with your storage: + +``` +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:@"mydatabase.sqlite"]; + + self.persistentTaskQueue = [[EDQueue alloc] initWithPersistentStore:fmdbBasedStorage]; +} +``` YourAppDelegate.h ```objective-c @@ -19,28 +34,15 @@ YourAppDelegate.h YourAppDelegate.m ```objective-c -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:@"database.sample.sqlite"]; - - self.persistentTaskQueue = [[EDQueue alloc] initWithPersistentStore:fmdbBasedStorage]; - - /* There you go ^^ */ - - return YES; -} - -// - - (void)applicationDidBecomeActive:(UIApplication *)application { - [self.persistentTaskQueue setDelegate:self]; - [self.persistentTaskQueue start]; + [[EDQueue defaultQueue] setDelegate:self]; + [[EDQueue defaultQueue] start]; } - (void)applicationWillResignActive:(UIApplication *)application { - [self.persistentTaskQueue stop]; + [[EDQueue defaultQueue] stop]; } - (void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(void (^)(EDQueueResult))block @@ -48,9 +50,9 @@ YourAppDelegate.m sleep(1); @try { - if ([job.task isEqualToString:@"success"]) { + if ([job.tag isEqualToString:@"success"]) { block(EDQueueResultSuccess); - } else if ([job.task isEqualToString:@"fail"]) { + } else if ([job.tag isEqualToString:@"fail"]) { block(EDQueueResultFail); } else { block(EDQueueResultCritical); @@ -64,16 +66,17 @@ YourAppDelegate.m SomewhereElse.m ```objective-c -[self.queue enqueueWithData:@{ @"foo" : @"bar" } forTask:@"nyancat"]; +EDQueueJob *success = [[EDQueueJob alloc] initWithTag:@"success" userInfo:@{ @"nyan" : @"cat" }]; +[[EDQueue defaultQueue] enqueueJob:success]; ``` -Explanation to type of `EDQueueResult` which permits three distinct states: +In order to keep things simple, the delegate method expects a call of a callback with one parameter type of `EDQueueResult` which permits three distinct states: - `EDQueueResultSuccess`: Used to indicate that a job has completed successfully - `EDQueueResultFail`: Used to indicate that a job has failed and should be retried (up to the specified `retryLimit`) - `EDQueueResultCritical`: Used to indicate that a job has failed critically and should not be attempted again ### Handling Async Jobs -As of v0.7.3 queue switched to delegate method suited for handling asyncronous jobs such as HTTP requests or [Disk I/O](https://github.com/thisandagain/storage): +As of v1.0 queue switched to a delegate method suited for handling asyncronous jobs such as HTTP requests or [Disk I/O](https://github.com/thisandagain/storage): ```objective-c @@ -97,11 +100,11 @@ As of v0.7.3 queue switched to delegate method suited for handling asyncronous j ``` ### Introspection -As of v0.7.0 queue includes a collection of methods to aid in queue introspection specific to each task: +As of v0.7.0 queue includes a collection of methods to aid in queue introspection specific to each task, using tags: ```objective-c -- (Boolean)jobExistsForTask:(NSString *)task; -- (Boolean)jobIsActiveForTask:(NSString *)task; -- (EDQueueJob *)nextJobForTask:(NSString *)task; +- (Boolean)jobExistsForTag:(NSString *)tag; +- (Boolean)jobIsActiveForTag:(NSString *)tag; +- (EDQueueJob *)nextJobForTag:(NSString *)tag; ``` --- @@ -114,9 +117,9 @@ As of v0.7.0 queue includes a collection of methods to aid in queue introspectio - (void)stop; - (void)empty; -- (Boolean)jobExistsForTask:(NSString *)task; -- (Boolean)jobIsActiveForTask:(NSString *)task; -- (EDQueueJob *)nextJobForTask:(NSString *)task; +- (Boolean)jobExistsForTag:(NSString *)tag; +- (Boolean)jobIsActiveForTag:(NSString *)tag; +- (EDQueueJob *)nextJobForTag:(NSString *)tag; ``` ### Delegate Methods @@ -155,6 +158,3 @@ EDQueue is designed for iOS 7 and up. ### ARC EDQueue is built using ARC. If you are including EDQueue in a project that **does not** use [Automatic Reference Counting (ARC)](http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html), you will need to set the `-fobjc-arc` compiler flag on all of the EDQueue source files. To do this in Xcode, go to your active target and select the "Build Phases" tab. Now select all EDQueue source files, press Enter, insert `-fobjc-arc` and then "Done" to enable ARC for EDQueue. - -### Nullability -EDQueue is growing up to be used with Swift later. Therefore it's code is being modernized, and wrapped nicely with 'nullable' markers \ No newline at end of file From 474a202f31c261ab8322e417032dc60483b5e814 Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Wed, 24 Feb 2016 17:27:23 +0100 Subject: [PATCH 09/14] EDQueueJob got expiration date, maxRetryCount, retryTimeInterval; EDQueue lost retryCount respectively; More Tests! --- EDQueue/EDQueue.h | 4 - EDQueue/EDQueue.m | 42 ++++-- EDQueue/EDQueueJob.h | 6 + EDQueue/EDQueueJob.m | 3 + EDQueue/EDQueuePersistentStorageProtocol.h | 7 +- EDQueue/EDQueueStorageEngine.m | 64 +++++---- Project/queue.xcodeproj/project.pbxproj | 4 + .../queueTests/EDQueueStorageEngineTests.m | 129 ++++++++++++++++++ 8 files changed, 211 insertions(+), 48 deletions(-) create mode 100644 Project/queueTests/EDQueueStorageEngineTests.m diff --git a/EDQueue/EDQueue.h b/EDQueue/EDQueue.h index 05edf30..75c6440 100755 --- a/EDQueue/EDQueue.h +++ b/EDQueue/EDQueue.h @@ -46,10 +46,6 @@ extern NSString *const EDQueueDidDrain; * Returns true if Queue is performing Job right now */ @property (nonatomic, readonly) BOOL isActive; -/** - * Retry limit for failing jobs (will be elimitated and moved to Job later) - */ -@property (nonatomic) NSUInteger retryLimit; + (instancetype)defaultQueue; diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index 56c9359..793cbe4 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -48,7 +48,6 @@ - (instancetype)initWithPersistentStore:(id)persistent { self = [super init]; if (self) { - _retryLimit = 4; _storage = persistentStore; } return self; @@ -105,7 +104,7 @@ - (BOOL)jobIsActiveForTag:(NSString *)tag */ - (nullable EDQueueJob *)nextJobForTag:(NSString *)tag { - id item = [self.storage fetchNextJobForTag:tag]; + id item = [self.storage fetchNextJobForTag:tag validForDate:[NSDate date]]; return item.job; } @@ -168,16 +167,25 @@ - (void)tick dispatch_queue_t gcd = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); dispatch_async(gcd, ^{ if (self.isRunning && !self.isActive && [self.storage jobCount] > 0) { - // Start job - _isActive = YES; - id storedJob = [self.storage fetchNextJob]; + + id storedJob = [self.storage fetchNextJobValidForDate:[NSDate date]]; + + if (!storedJob) { + __weak typeof(self) weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [weakSelf performSelectorOnMainThread:@selector(tick) withObject:nil waitUntilDone:false]; + }); + + return; + } + + // Start job & Pass job to delegate self.activeJobTag = storedJob.job.tag; - - // Pass job to delegate - [self.delegate queue:self processJob:storedJob.job completion:^(EDQueueResult result) { - [self processJob:storedJob withResult:result]; - self.activeJobTag = nil; - }]; + _isActive = YES; + [self.delegate queue:self processJob:storedJob.job completion:^(EDQueueResult result) { + [self processJob:storedJob withResult:result]; + self.activeJobTag = nil; + }]; } }); } @@ -203,10 +211,16 @@ - (void)processJob:(id)storedJob withResult:(EDQueueResult)r EDQueueDataKey : storedJob.job }]; - NSUInteger currentAttempt = storedJob.attempts.integerValue + 1; + BOOL shouldRetry = NO; + + if (storedJob.job.maxRetryCount == EDQueueJobInfiniteRetryCount) { + shouldRetry = YES; + } else if(storedJob.job.maxRetryCount > storedJob.attempts.integerValue) { + shouldRetry = YES; + } - if (currentAttempt < self.retryLimit) { - [self.storage incrementAttemptForJob:storedJob]; + if (shouldRetry) { + [self.storage scheduleNextAttemptForJob:storedJob]; } else { [self.storage removeJob:storedJob]; } diff --git a/EDQueue/EDQueueJob.h b/EDQueue/EDQueueJob.h index 05bb185..21f621d 100644 --- a/EDQueue/EDQueueJob.h +++ b/EDQueue/EDQueueJob.h @@ -10,11 +10,17 @@ NS_ASSUME_NONNULL_BEGIN +static NSUInteger const EDQueueJobInfiniteRetryCount = 0; +static NSTimeInterval const EDQueueJobDefaultRetryTimeInterval = 15.0; + @interface EDQueueJob : NSObject @property(nonatomic, readonly) NSString *tag; @property(nonatomic, readonly) NSDictionary, id> *userInfo; +@property(nonatomic, readwrite) NSUInteger maxRetryCount; +@property(nonatomic, readwrite) NSTimeInterval retryTimeInterval; +@property(nonatomic) NSDate *expirationDate; - (instancetype)initWithTag:(NSString *)tag userInfo:(nullable NSDictionary, id> *)userInfo; diff --git a/EDQueue/EDQueueJob.m b/EDQueue/EDQueueJob.m index 14e0e6a..4aa06d1 100644 --- a/EDQueue/EDQueueJob.m +++ b/EDQueue/EDQueueJob.m @@ -20,6 +20,9 @@ - (instancetype)initWithTag:(NSString *)tag if (self) { _tag = [tag copy]; _userInfo = userInfo ? [userInfo copy] : @{}; + _maxRetryCount = EDQueueJobInfiniteRetryCount; + _retryTimeInterval = EDQueueJobDefaultRetryTimeInterval; + _expirationDate = [NSDate distantFuture]; } return self; diff --git a/EDQueue/EDQueuePersistentStorageProtocol.h b/EDQueue/EDQueuePersistentStorageProtocol.h index d575d0f..65b6d22 100644 --- a/EDQueue/EDQueuePersistentStorageProtocol.h +++ b/EDQueue/EDQueuePersistentStorageProtocol.h @@ -19,7 +19,6 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly, nullable) NSNumber *jobID; @property(nonatomic, readonly, nullable) NSNumber *attempts; -@property(nonatomic, readonly, nullable) NSString *timeStamp; @end @@ -28,14 +27,14 @@ NS_ASSUME_NONNULL_BEGIN - (void)createJob:(EDQueueJob *)job; - (BOOL)jobExistsForTag:(NSString *)tag; -- (void)incrementAttemptForJob:(id)jid; +- (void)scheduleNextAttemptForJob:(id)jid; - (void)removeJob:(id)jid; - (void)removeAllJobs; - (NSUInteger)jobCount; -- (nullable id)fetchNextJob; -- (nullable id)fetchNextJobForTag:(NSString *)tag; +- (nullable id)fetchNextJobValidForDate:(NSDate *)date; +- (nullable id)fetchNextJobForTag:(NSString *)tag validForDate:(NSDate *)date; @end diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index 1387314..a3106d6 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -31,8 +31,7 @@ @interface EDQueueStorageEngineJob : NSObject - (instancetype)initWithTag:(NSString *)tag userInfo:(nullable NSDictionary, id> *)userInfo jobID:(nullable NSNumber *)jobID - atempts:(nullable NSNumber *)attemps - timeStamp:(nullable NSString *)timeStamp; + atempts:(nullable NSNumber *)attemps; @end @@ -41,13 +40,11 @@ @implementation EDQueueStorageEngineJob @synthesize job = _job; @synthesize jobID = _jobID; @synthesize attempts = _attempts; -@synthesize timeStamp = _timeStamp; - (instancetype)initWithTag:(NSString *)tag userInfo:(nullable NSDictionary, id> *)userInfo jobID:(nullable NSNumber *)jobID atempts:(nullable NSNumber *)attemps - timeStamp:(nullable NSString *)timeStamp { self = [super init]; @@ -56,7 +53,6 @@ - (instancetype)initWithTag:(NSString *)tag _job = [[EDQueueJob alloc] initWithTag:tag userInfo:userInfo]; _jobID = [jobID copy]; _attempts = [attemps copy]; - _timeStamp = [timeStamp copy]; } return self; @@ -92,7 +88,7 @@ - (nullable instancetype)initWithName:(NSString *)name } [self.queue inDatabase:^(FMDatabase *db) { - [db executeUpdate:@"CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY, tag TEXT NOT NULL, data TEXT NOT NULL, attempts INTEGER DEFAULT 0, stamp STRING DEFAULT (strftime('%s','now')) NOT NULL, udef_1 TEXT, udef_2 TEXT)"]; + [db executeUpdate:@"CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY, tag TEXT NOT NULL, data TEXT NOT NULL, attempts INTEGER DEFAULT 0, maxAttempts INTEGER DEFAULT 0, expiration DOUBLE DEFAULT 0, retryTimeInterval DOUBLE DEFAULT 30, lastAttempt DOUBLE DEFAULT 0 )"]; [self _databaseHadError:[db hadError] fromDatabase:db]; }]; } @@ -116,18 +112,26 @@ - (void)dealloc */ - (void)createJob:(EDQueueJob *)job { - NSString *dataString = nil; + if (!job.userInfo) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"EDQueueJob.userInfo can not be nil" userInfo:nil]; + } - if (job.userInfo) { - NSData *data = [NSJSONSerialization dataWithJSONObject:job.userInfo + NSData *data = [NSJSONSerialization dataWithJSONObject:job.userInfo options:NSJSONWritingPrettyPrinted error:nil]; - dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - } - + NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + [self.queue inDatabase:^(FMDatabase *db) { - [db executeUpdate:@"INSERT INTO queue (tag, data) VALUES (?, ?)", job.tag, dataString]; + + NSTimeInterval expiration = job.expirationDate.timeIntervalSince1970; + + if (expiration == 0) { + expiration = [NSDate distantFuture].timeIntervalSince1970; + } + + [db executeUpdate:@"INSERT INTO queue (tag, data, maxAttempts, expiration, retryTimeInterval) VALUES (?, ?, ?, ?, ?)", job.tag, dataString, @(job.maxRetryCount), @(expiration), @(job.retryTimeInterval)]; [self _databaseHadError:[db hadError] fromDatabase:db]; }]; } @@ -164,14 +168,14 @@ - (BOOL)jobExistsForTag:(NSString *)tag * * @return {void} */ -- (void)incrementAttemptForJob:(id)job +- (void)scheduleNextAttemptForJob:(id)job { if (!job.jobID) { return; } [self.queue inDatabase:^(FMDatabase *db) { - [db executeUpdate:@"UPDATE queue SET attempts = attempts + 1 WHERE id = ?", job.jobID]; + [db executeUpdate:@"UPDATE queue SET attempts = attempts + 1, lastAttempt = ? WHERE id = ?", @([NSDate date].timeIntervalSince1970 + job.job.retryTimeInterval), job.jobID]; [self _databaseHadError:[db hadError] fromDatabase:db]; }]; } @@ -218,7 +222,7 @@ - (NSUInteger)jobCount __block NSUInteger count = 0; [self.queue inDatabase:^(FMDatabase *db) { - FMResultSet *rs = [db executeQuery:@"SELECT count(id) AS count FROM queue"]; + FMResultSet *rs = [db executeQuery:@"SELECT count(id) AS count FROM queue WHERE expiration >= ? ",@([NSDate date].timeIntervalSince1970)]; [self _databaseHadError:[db hadError] fromDatabase:db]; while ([rs next]) { @@ -234,14 +238,15 @@ - (NSUInteger)jobCount /** * Returns the oldest job from the datastore. * - * @return {NSDictionary} + * @return {id} */ -- (nullable id)fetchNextJob +- (nullable id)fetchNextJobValidForDate:(NSDate *)date { __block id job; [self.queue inDatabase:^(FMDatabase *db) { - FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue ORDER BY id ASC LIMIT 1"]; + NSTimeInterval timestamp = date.timeIntervalSince1970; + FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE lastAttempt <= ? AND expiration >= ? ORDER BY id ASC LIMIT 1", @(timestamp), @(timestamp)]; [self _databaseHadError:[db hadError] fromDatabase:db]; while ([rs next]) { @@ -259,14 +264,14 @@ - (NSUInteger)jobCount * * @param {id} tag * - * @return {NSDictionary} + * @return {id} */ -- (nullable id)fetchNextJobForTag:(NSString *)tag +- (nullable id)fetchNextJobForTag:(NSString *)tag validForDate:(NSDate *)date { __block id job; [self.queue inDatabase:^(FMDatabase *db) { - FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE tag = ? ORDER BY id ASC LIMIT 1", tag]; + FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE tag = ? AND lastAttempt <= ? AND expiration >= ? ORDER BY id ASC LIMIT 1", tag, @(date.timeIntervalSince1970), @(date.timeIntervalSince1970)]; [self _databaseHadError:[db hadError] fromDatabase:db]; while ([rs next]) { @@ -285,13 +290,20 @@ - (NSUInteger)jobCount { NSDictionary *userInfo = [NSJSONSerialization JSONObjectWithData:[[rs stringForColumn:@"data"] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil]; - EDQueueStorageEngineJob *job = [[EDQueueStorageEngineJob alloc] initWithTag:[rs stringForColumn:@"tag"] + EDQueueStorageEngineJob *storedItem = [[EDQueueStorageEngineJob alloc] initWithTag:[rs stringForColumn:@"tag"] userInfo:userInfo jobID:@([rs intForColumn:@"id"]) - atempts:@([rs intForColumn:@"attempts"]) - timeStamp:[rs stringForColumn:@"stamp"]]; + atempts:@([rs intForColumn:@"attempts"])]; - return job; + storedItem.job.maxRetryCount = [rs unsignedLongLongIntForColumn:@"maxAttempts"]; + storedItem.job.retryTimeInterval = [rs doubleForColumn:@"retryTimeInterval"]; + + NSTimeInterval expiration = [rs doubleForColumn:@"expiration"]; + + storedItem.job.expirationDate = [NSDate dateWithTimeIntervalSince1970:expiration]; + + + return storedItem; } - (BOOL)_databaseHadError:(BOOL)flag fromDatabase:(FMDatabase *)db diff --git a/Project/queue.xcodeproj/project.pbxproj b/Project/queue.xcodeproj/project.pbxproj index 79c5a31..70960c4 100644 --- a/Project/queue.xcodeproj/project.pbxproj +++ b/Project/queue.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 7C57D1AD1C7DF458009C794E /* EDQueueStorageEngineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C57D1AC1C7DF458009C794E /* EDQueueStorageEngineTests.m */; }; 7CA53AF41C75F88E00420814 /* EDQueueJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CA53AF31C75F88E00420814 /* EDQueueJob.m */; }; 7CA53AF61C7733E400420814 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 7CA53AF51C7733E400420814 /* Default-568h@2x.png */; }; 7CA53B061C77496800420814 /* EDQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CA53B051C77496800420814 /* EDQueueTests.m */; }; @@ -42,6 +43,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7C57D1AC1C7DF458009C794E /* EDQueueStorageEngineTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EDQueueStorageEngineTests.m; sourceTree = ""; }; 7CA53AF21C75F88E00420814 /* EDQueueJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EDQueueJob.h; path = ../EDQueue/EDQueueJob.h; sourceTree = ""; }; 7CA53AF31C75F88E00420814 /* EDQueueJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EDQueueJob.m; path = ../EDQueue/EDQueueJob.m; sourceTree = ""; }; 7CA53AF51C7733E400420814 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; @@ -108,6 +110,7 @@ isa = PBXGroup; children = ( 7CA53B051C77496800420814 /* EDQueueTests.m */, + 7C57D1AC1C7DF458009C794E /* EDQueueStorageEngineTests.m */, 7CA53AFF1C77495200420814 /* Info.plist */, ); path = queueTests; @@ -313,6 +316,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7C57D1AD1C7DF458009C794E /* EDQueueStorageEngineTests.m in Sources */, 7CA53B061C77496800420814 /* EDQueueTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Project/queueTests/EDQueueStorageEngineTests.m b/Project/queueTests/EDQueueStorageEngineTests.m new file mode 100644 index 0000000..4e69ab3 --- /dev/null +++ b/Project/queueTests/EDQueueStorageEngineTests.m @@ -0,0 +1,129 @@ +// +// EDQueueStorageEngineTests.m +// queue +// +// Created by Oleg Shanyuk on 24/02/16. +// Copyright © 2016 DIY, Co. All rights reserved. +// + +#import +#import "EDQueueStorageEngine.h" +#import "EDQueueJob.h" + +@interface EDQueueStorageEngineTests : XCTestCase + +@end + +@implementation EDQueueStorageEngineTests + +- (void)tearDown { + + [EDQueueStorageEngine deleteDatabaseName:@"test.db"]; + + [super tearDown]; +} + +- (void)testDefaultJobAdded +{ + EDQueueStorageEngine *testEngine = [[EDQueueStorageEngine alloc] initWithName:@"test.db"]; + + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"tag" userInfo:@{@"user":@"info"}]; + + [testEngine createJob:job]; + + id item = [testEngine fetchNextJobValidForDate:[NSDate date]]; + + XCTAssertNotNil(item); + + XCTAssertEqual(item.job.expirationDate, [NSDate distantFuture]); + XCTAssertEqual(item.job.retryTimeInterval, job.retryTimeInterval); + XCTAssertEqual(item.job.maxRetryCount, job.maxRetryCount); + + XCTAssertEqualObjects(item.job.userInfo, job.userInfo); + XCTAssertEqualObjects(item.job.tag, job.tag); +} + +- (void)testAddExpiredJob +{ + EDQueueStorageEngine *testEngine = [[EDQueueStorageEngine alloc] initWithName:@"test.db"]; + + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"tag" userInfo:@{@"user":@"info"}]; + + job.expirationDate = [[NSDate date] dateByAddingTimeInterval:-1]; + + id item = [testEngine fetchNextJobValidForDate:[NSDate date]]; + + XCTAssertNil(item); +} + +- (void)testJobCountOnEmptyDatabase +{ + EDQueueStorageEngine *testEngine = [[EDQueueStorageEngine alloc] initWithName:@"test.db"]; + + XCTAssertEqual([testEngine jobCount], 0); +} + +- (void)testJobCountWithExpiredJobs +{ + EDQueueStorageEngine *testEngine = [[EDQueueStorageEngine alloc] initWithName:@"test.db"]; + + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"tag" userInfo:@{@"user":@"info"}]; + + job.expirationDate = [[NSDate date] dateByAddingTimeInterval:-1]; + + XCTAssertEqual([testEngine jobCount], 0); +} + +- (void)testJobCountWithOneJob +{ + EDQueueStorageEngine *testEngine = [[EDQueueStorageEngine alloc] initWithName:@"test.db"]; + + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"tag" userInfo:@{@"user":@"info"}]; + + [testEngine createJob:job]; + + XCTAssertEqual([testEngine jobCount], 1); +} + + +- (void)testAddJobThatExpires +{ + EDQueueStorageEngine *testEngine = [[EDQueueStorageEngine alloc] initWithName:@"test.db"]; + + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"tag" userInfo:@{@"user":@"info"}]; + + NSDate *expirationDate = [NSDate date]; + + job.expirationDate = expirationDate; + + [testEngine createJob:job]; + + id itemInvalid = [testEngine fetchNextJobValidForDate:[expirationDate dateByAddingTimeInterval:1]]; + + XCTAssertNil(itemInvalid); + + id itemValid = [testEngine fetchNextJobValidForDate:[expirationDate dateByAddingTimeInterval:-1]]; + + XCTAssertNotNil(itemValid); +} + +- (void)testScheduleNextAttemptForJob +{ + EDQueueStorageEngine *testEngine = [[EDQueueStorageEngine alloc] initWithName:@"test.db"]; + + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"tag" userInfo:@{@"user":@"info"}]; + + [testEngine createJob:job]; + + id item = [testEngine fetchNextJobValidForDate:[NSDate date]]; + + XCTAssertEqual(item.attempts.integerValue, 0); + + [testEngine scheduleNextAttemptForJob:item]; + + id itemIncreased = [testEngine fetchNextJobValidForDate:[[NSDate date] dateByAddingTimeInterval:job.retryTimeInterval]]; + + XCTAssertEqual(itemIncreased.attempts.integerValue, 1); +} + +@end From 82a64aa3e67125b545d707b05fbbf3b09be2adab Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Thu, 25 Feb 2016 01:22:53 +0100 Subject: [PATCH 10/14] Removes expired jobs along with removing job by ID --- EDQueue/EDQueueStorageEngine.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index a3106d6..42e1624 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -87,7 +87,7 @@ - (nullable instancetype)initWithName:(NSString *)name return nil; } - [self.queue inDatabase:^(FMDatabase *db) { + [_queue inDatabase:^(FMDatabase *db) { [db executeUpdate:@"CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY, tag TEXT NOT NULL, data TEXT NOT NULL, attempts INTEGER DEFAULT 0, maxAttempts INTEGER DEFAULT 0, expiration DOUBLE DEFAULT 0, retryTimeInterval DOUBLE DEFAULT 30, lastAttempt DOUBLE DEFAULT 0 )"]; [self _databaseHadError:[db hadError] fromDatabase:db]; }]; @@ -181,7 +181,8 @@ - (void)scheduleNextAttemptForJob:(id)job } /** - * Removes a job from the datastore using a specified id. + * Removes a job from the datastore using a specified id. And also removes all Expired jobs. + * (aka clean-up) * * @param {NSNumber} Job id * @@ -194,7 +195,8 @@ - (void)removeJob:(id)job } [self.queue inDatabase:^(FMDatabase *db) { - [db executeUpdate:@"DELETE FROM queue WHERE id = ?", job.jobID]; + NSNumber *expiration = @([NSDate date].timeIntervalSince1970); + [db executeUpdate:@"DELETE FROM queue WHERE id = ? OR expiration < ?", job.jobID, expiration]; [self _databaseHadError:[db hadError] fromDatabase:db]; }]; } @@ -205,7 +207,8 @@ - (void)removeJob:(id)job * @return {void} * */ -- (void)removeAllJobs { +- (void)removeAllJobs +{ [self.queue inDatabase:^(FMDatabase *db) { [db executeUpdate:@"DELETE FROM queue"]; [self _databaseHadError:[db hadError] fromDatabase:db]; From 12e0b86d21814179eabf1f2d0252a66056589898 Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Fri, 26 Feb 2016 16:22:34 +0100 Subject: [PATCH 11/14] Queue got jobCount & better scheduling though storage requirement (`fetchNextJobTimeInterval`). Also, fixed critical bug - a race condition in queue. So, job might be executed two times :/ Hola TDD! --- EDQueue.podspec | 4 +- EDQueue/EDQueue.h | 2 + EDQueue/EDQueue.m | 44 +- EDQueue/EDQueuePersistentStorageProtocol.h | 1 + EDQueue/EDQueueStorageEngine.m | 59 ++- Project/queue/EDViewController.h | 2 + Project/queue/EDViewController.m | 8 + Project/queue/en.lproj/EDViewController.xib | 456 +++--------------- .../queueTests/EDQueueStorageEngineTests.m | 17 + Project/queueTests/EDQueueTests.m | 49 +- 10 files changed, 236 insertions(+), 406 deletions(-) diff --git a/EDQueue.podspec b/EDQueue.podspec index d3950d3..791da12 100644 --- a/EDQueue.podspec +++ b/EDQueue.podspec @@ -3,9 +3,9 @@ Pod::Spec.new do |s| s.version = '1.0' s.license = 'MIT' s.summary = 'A persistent background job queue for iOS.' - s.homepage = 'https://github.com/thisandagain/queue' + s.homepage = 'https://github.com/gelosi/queue' s.authors = {'Andrew Sliwinski' => 'andrewsliwinski@acm.org', 'Francois Lambert' => 'flambert@mirego.com', 'Oleg Shanyuk' => 'oleg.shanyuk@gmail.com'} - s.source = { :git => 'https://github.com/thisandagain/queue.git', :tag => 'v1.0' } + s.source = { :git => 'https://github.com/gelosi/queue.git', :tag => 'v1.0' } s.platform = :ios, '7.0' s.source_files = 'EDQueue' s.library = 'sqlite3.0' diff --git a/EDQueue/EDQueue.h b/EDQueue/EDQueue.h index 75c6440..0b0a636 100755 --- a/EDQueue/EDQueue.h +++ b/EDQueue/EDQueue.h @@ -56,6 +56,8 @@ extern NSString *const EDQueueDidDrain; - (void)stop; - (void)empty; +- (NSInteger)jobCount; + - (BOOL)jobExistsForTag:(NSString *)tag; - (BOOL)jobIsActiveForTag:(NSString *)tag; - (nullable EDQueueJob *)nextJobForTag:(NSString *)tag; diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index 793cbe4..81485e5 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -36,7 +36,7 @@ + (instancetype)defaultQueue static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:@"edqueue.default.v7.3.sqlite"]; + EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:@"edqueue.default.v1.0.sqlite"]; defaultQueue = [[EDQueue alloc] initWithPersistentStore:fmdbBasedStorage]; }); @@ -56,6 +56,16 @@ - (instancetype)initWithPersistentStore:(id)persistent #pragma mark - Public methods +/** + * Total number of enqueued & valid jobs. + * + * @return {NSInteger} + */ +- (NSInteger)jobCount +{ + return [self.storage jobCount]; +} + /** * Adds a new job to the queue. * @@ -152,6 +162,8 @@ - (void)stop - (void)empty { [self.storage removeAllJobs]; + + [self postNotificationOnMainThread:@{ EDQueueNameKey : EDQueueDidDrain }]; } @@ -164,15 +176,31 @@ - (void)empty */ - (void)tick { - dispatch_queue_t gcd = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); - dispatch_async(gcd, ^{ - if (self.isRunning && !self.isActive && [self.storage jobCount] > 0) { + static dispatch_once_t onceToken; + static dispatch_queue_t gcd; + + dispatch_once(&onceToken, ^{ + gcd = dispatch_queue_create("edqueue.serial", DISPATCH_QUEUE_SERIAL); + }); + + dispatch_barrier_async(gcd, ^{ + + if (!self.isRunning) { + return; + } + + if (self.isActive) { + return; + } + + if ([self.storage jobCount] > 0) { id storedJob = [self.storage fetchNextJobValidForDate:[NSDate date]]; if (!storedJob) { __weak typeof(self) weakSelf = self; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + NSTimeInterval nextTime = [self.storage fetchNextJobTimeInterval]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(nextTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakSelf performSelectorOnMainThread:@selector(tick) withObject:nil waitUntilDone:false]; }); @@ -182,6 +210,7 @@ - (void)tick // Start job & Pass job to delegate self.activeJobTag = storedJob.job.tag; _isActive = YES; + [self.delegate queue:self processJob:storedJob.job completion:^(EDQueueResult result) { [self processJob:storedJob withResult:result]; self.activeJobTag = nil; @@ -202,6 +231,7 @@ - (void)processJob:(id)storedJob withResult:(EDQueueResult)r }]; [self.storage removeJob:storedJob]; + break; case EDQueueResultFail: @@ -224,6 +254,7 @@ - (void)processJob:(id)storedJob withResult:(EDQueueResult)r } else { [self.storage removeJob:storedJob]; } + break; case EDQueueResultCritical: @@ -234,12 +265,13 @@ - (void)processJob:(id)storedJob withResult:(EDQueueResult)r [self errorWithMessage:@"Critical error. Job canceled."]; [self.storage removeJob:storedJob]; + break; } // Clean-up _isActive = NO; - + // Drain if ([self.storage jobCount] == 0) { diff --git a/EDQueue/EDQueuePersistentStorageProtocol.h b/EDQueue/EDQueuePersistentStorageProtocol.h index 65b6d22..53589fc 100644 --- a/EDQueue/EDQueuePersistentStorageProtocol.h +++ b/EDQueue/EDQueuePersistentStorageProtocol.h @@ -35,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSUInteger)jobCount; - (nullable id)fetchNextJobValidForDate:(NSDate *)date; - (nullable id)fetchNextJobForTag:(NSString *)tag validForDate:(NSDate *)date; +- (NSTimeInterval)fetchNextJobTimeInterval; @end diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index 42e1624..2e98e54 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -15,6 +15,10 @@ #import "EDQueueJob.h" +static NSTimeInterval const DefaultJobSleepInteval = 5.0; +static NSTimeInterval const MaxJobSleepInterval = 60.0; + + NS_ASSUME_NONNULL_BEGIN static NSString *pathForStorageName(NSString *storage) @@ -58,6 +62,11 @@ - (instancetype)initWithTag:(NSString *)tag return self; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ : id:%@, tag: %@",NSStringFromClass([self class]), _jobID, _job.tag]; +} + @end @interface EDQueueStorageEngine() @@ -239,7 +248,7 @@ - (NSUInteger)jobCount } /** - * Returns the oldest job from the datastore. + * Returns the oldest valid job from the datastore. * * @return {id} */ @@ -263,28 +272,60 @@ - (NSUInteger)jobCount } /** - * Returns the oldest job for the with tag from the datastore. - * - * @param {id} tag + * Returns the oldest valid job from the datastore with specific tag * * @return {id} */ - (nullable id)fetchNextJobForTag:(NSString *)tag validForDate:(NSDate *)date { __block id job; - + [self.queue inDatabase:^(FMDatabase *db) { - FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE tag = ? AND lastAttempt <= ? AND expiration >= ? ORDER BY id ASC LIMIT 1", tag, @(date.timeIntervalSince1970), @(date.timeIntervalSince1970)]; + NSTimeInterval timestamp = date.timeIntervalSince1970; + FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE tag = ? AND lastAttempt <= ? AND expiration >= ? ORDER BY id ASC LIMIT 1", tag, @(timestamp), @(timestamp)]; [self _databaseHadError:[db hadError] fromDatabase:db]; - + while ([rs next]) { job = [self _jobFromResultSet:rs]; } + + [rs close]; + }]; + + return job; +} + +/** + * Returns the minumum timeout for starting the next job. + * + * @param {id} tag + * + * @return {id} + */ +- (NSTimeInterval)fetchNextJobTimeInterval +{ + + __block NSTimeInterval timeInterval = DefaultJobSleepInteval; + + [self.queue inDatabase:^(FMDatabase *db) { + + FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE lastAttempt < expiration ORDER BY lastAttempt ASC LIMIT 1"]; + [self _databaseHadError:[db hadError] fromDatabase:db]; + + while ([rs next]) { + timeInterval = [rs doubleForColumn:@"lastAttempt"]; + + timeInterval = fabs(timeInterval - [NSDate date].timeIntervalSince1970); + + if (timeInterval > MaxJobSleepInterval) { + timeInterval = MaxJobSleepInterval; + } + } [rs close]; }]; - return job; + return timeInterval; } #pragma mark - Private methods @@ -298,7 +339,7 @@ - (NSUInteger)jobCount jobID:@([rs intForColumn:@"id"]) atempts:@([rs intForColumn:@"attempts"])]; - storedItem.job.maxRetryCount = [rs unsignedLongLongIntForColumn:@"maxAttempts"]; + storedItem.job.maxRetryCount = [rs intForColumn:@"maxAttempts"]; storedItem.job.retryTimeInterval = [rs doubleForColumn:@"retryTimeInterval"]; NSTimeInterval expiration = [rs doubleForColumn:@"expiration"]; diff --git a/Project/queue/EDViewController.h b/Project/queue/EDViewController.h index b95f5d3..32690c3 100644 --- a/Project/queue/EDViewController.h +++ b/Project/queue/EDViewController.h @@ -11,10 +11,12 @@ @interface EDViewController : UIViewController +@property (weak, nonatomic) IBOutlet UILabel *activityTitle; @property (nonatomic) IBOutlet UITextView *activity; - (IBAction)addSuccess:(id)sender; - (IBAction)addFail:(id)sender; - (IBAction)addCritical:(id)sender; +- (IBAction)clearQueue:(id)sender; @end \ No newline at end of file diff --git a/Project/queue/EDViewController.m b/Project/queue/EDViewController.m index 1ab988d..d8bf3c4 100644 --- a/Project/queue/EDViewController.m +++ b/Project/queue/EDViewController.m @@ -43,6 +43,7 @@ - (IBAction)addSuccess:(id)sender - (IBAction)addFail:(id)sender { EDQueueJob *fail = [[EDQueueJob alloc] initWithTag:@"fail" userInfo:nil]; + fail.maxRetryCount = 10; [[EDQueue defaultQueue] enqueueJob:fail]; } @@ -51,6 +52,11 @@ - (IBAction)addCritical:(id)sender EDQueueJob *critical = [[EDQueueJob alloc] initWithTag:@"critical" userInfo:nil]; [[EDQueue defaultQueue] enqueueJob:critical]; } + +- (IBAction)clearQueue:(id)sender +{ + [[EDQueue defaultQueue] empty]; +} #pragma mark - Notifications @@ -58,6 +64,8 @@ - (void)receivedNotification:(NSNotification *)notification { self.activity.text = [NSString stringWithFormat:@"%@%@\n", self.activity.text, notification]; [self.activity scrollRangeToVisible:NSMakeRange([self.activity.text length], 0)]; + + self.activityTitle.text = [NSString stringWithFormat:@"Activity: %ld",(long)[[EDQueue defaultQueue] jobCount]]; } #pragma mark - Dealloc diff --git a/Project/queue/en.lproj/EDViewController.xib b/Project/queue/en.lproj/EDViewController.xib index a822a86..4f0ba13 100644 --- a/Project/queue/en.lproj/EDViewController.xib +++ b/Project/queue/en.lproj/EDViewController.xib @@ -1,376 +1,80 @@ - - - - 1296 - 11E53 - 2182 - 1138.47 - 569.00 - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 1181 - - - IBUITextView - IBUIButton - IBUIView - IBUILabel - IBProxyObject - - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - PluginDependencyRecalculationVersion - - - - - IBFilesOwner - IBCocoaTouchFramework - - - IBFirstResponder - IBCocoaTouchFramework - - - - 274 - - - - 292 - {{20, 403}, {280, 37}} - - - - _NS:9 - NO - IBCocoaTouchFramework - 0 - 0 - 1 - Add A Critically Bad Job - - 3 - MQA - - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - - - 3 - MC41AA - - - 2 - 15 - - - Helvetica-Bold - 15 - 16 - - - - - 292 - {{20, 358}, {280, 37}} - - - - _NS:9 - NO - IBCocoaTouchFramework - 0 - 0 - 1 - Add A Bad Job - - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - - - - - - - - 292 - {{20, 313}, {280, 37}} - - - - _NS:9 - NO - IBCocoaTouchFramework - 0 - 0 - 1 - Add A Good Job - - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - - - - - - - - 292 - {{20, 20}, {61, 21}} - - - - _NS:9 - NO - YES - 7 - NO - IBCocoaTouchFramework - Activity: - - 1 - MCAwIDAAA - - - 0 - 10 - - 1 - 17 - - - Helvetica - 17 - 16 - - - - - 292 - {{20, 49}, {280, 240}} - - - - _NS:9 - - 1 - MSAxIDEAA - - YES - YES - IBCocoaTouchFramework - - - 2 - IBCocoaTouchFramework - - - Courier - Courier - 0 - 10 - - - Courier - 10 - 16 - - - - {{0, 20}, {320, 460}} - - - - - 3 - MC43NQA - - 2 - - - NO - - IBCocoaTouchFramework - - - - - - - view - - - - 7 - - - - activity - - - - 11 - - - - addCritical: - - - 7 - - 20 - - - - addFail: - - - 7 - - 19 - - - - addSuccess: - - - 7 - - 18 - - - - - - 0 - - - - - - -1 - - - File's Owner - - - -2 - - - - - 6 - - - - - - - - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 14 - - - - - 16 - - - - - - - EDViewController - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - UIResponder - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - - - - 20 - - - - - EDViewController - UIViewController - - id - id - id - - - - addCritical: - id - - - addFail: - id - - - addSuccess: - id - - - - activity - UITextView - - - activity - - activity - UITextView - - - - IBProjectSource - ./Classes/EDViewController.h - - - - - 0 - IBCocoaTouchFramework - - com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS - - - YES - 3 - 1181 - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Project/queueTests/EDQueueStorageEngineTests.m b/Project/queueTests/EDQueueStorageEngineTests.m index 4e69ab3..dbc6919 100644 --- a/Project/queueTests/EDQueueStorageEngineTests.m +++ b/Project/queueTests/EDQueueStorageEngineTests.m @@ -126,4 +126,21 @@ - (void)testScheduleNextAttemptForJob XCTAssertEqual(itemIncreased.attempts.integerValue, 1); } +- (void)testFetchNextJobTimeInterval +{ + EDQueueStorageEngine *testEngine = [[EDQueueStorageEngine alloc] initWithName:@"test.db"]; + + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"tag" userInfo:@{@"user":@"info"}]; + + [testEngine createJob:job]; + + id item = [testEngine fetchNextJobValidForDate:[NSDate date]]; + + [testEngine scheduleNextAttemptForJob:item]; + + NSTimeInterval nextTimeInterval = [testEngine fetchNextJobTimeInterval]; + + XCTAssertEqualWithAccuracy(nextTimeInterval, job.retryTimeInterval, 2); +} + @end diff --git a/Project/queueTests/EDQueueTests.m b/Project/queueTests/EDQueueTests.m index ca39ce8..e11feea 100644 --- a/Project/queueTests/EDQueueTests.m +++ b/Project/queueTests/EDQueueTests.m @@ -10,18 +10,25 @@ #import "EDQueue.h" #import "EDQueueStorageEngine.h" -NSString *const EQTestDatabaseName = @"database.test.sqlite"; +static NSString *const EQTestDatabaseName = @"database.test.sqlite"; +static NSString *const EQExpectationKey = @"EK"; @interface EDQueueTests : XCTestCase @property (nonatomic) EDQueue *queue; -@property (nonatomic) XCTestExpectation *currentExpectation; +@property (nonatomic) NSMutableDictionary *expectationHashes; @end @implementation EDQueueTests -(void)queue:(EDQueue *)queue processJob:(EDQueueJob *)job completion:(EDQueueCompletionBlock)block { - [self.currentExpectation fulfill]; + NSString *expectationHash = (NSString *)job.userInfo[EQExpectationKey]; + + XCTestExpectation *expectation = self.expectationHashes[expectationHash]; + + NSLog(@"Testing(%p): %@ -> %@", self, expectationHash, expectation); + + [expectation fulfill]; block(EDQueueResultSuccess); } @@ -31,15 +38,15 @@ - (void)setUp { self.queue = [[EDQueue alloc] initWithPersistentStore:fmdbBasedStorage]; - self.currentExpectation = nil; + self.queue.delegate = self; + + self.expectationHashes = [NSMutableDictionary dictionary]; } - (void)tearDown { self.queue = nil; - self.currentExpectation = nil; - [EDQueueStorageEngine deleteDatabaseName:EQTestDatabaseName]; } @@ -60,26 +67,32 @@ - (void)testQueueStop - (void)testQueueStartThenAddJob { - EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; + XCTestExpectation *expectation = [self expectationWithDescription:@"testQueueStartThenAddJob"]; - self.queue.delegate = self; + NSString *expectationHash = [NSString stringWithFormat:@"%p", expectation]; + + self.expectationHashes[expectationHash] = expectation; + + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{EQExpectationKey : expectationHash}]; - self.currentExpectation = [self expectationWithDescription:@"queue should start soon"]; + NSLog(@"Added: %@", expectationHash); [self.queue start]; [self.queue enqueueJob:job]; - [self waitForExpectationsWithTimeout:0.5 handler:nil]; + [self waitForExpectationsWithTimeout:1000.5 handler:nil]; } - (void)testQueueAddJobThenStart { - EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; + XCTestExpectation *expectation = [self expectationWithDescription:@"testQueueAddJobThenStart"]; - self.queue.delegate = self; + NSString *expectationHash = [NSString stringWithFormat:@"%p", expectation]; - self.currentExpectation = [self expectationWithDescription:@"queue should start soon"]; + self.expectationHashes[expectationHash] = expectation; + + EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{EQExpectationKey : expectationHash}]; [self.queue enqueueJob:job]; @@ -92,6 +105,8 @@ - (void)testJobExistsForTagAndEmpty { EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; + self.queue.delegate = nil; // queue won't be able to complete task w/o delegate. so, we have time to check it + [self.queue enqueueJob:job]; XCTAssertTrue([self.queue jobExistsForTag:@"testTask"]); @@ -105,6 +120,8 @@ - (void)testJobDoesNotExistForTag { EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; + self.queue.delegate = nil; // queue won't be able to complete task w/o delegate. so, we have time to check it + [self.queue enqueueJob:job]; XCTAssertFalse([self.queue jobExistsForTag:@"testTaskFalse"]); @@ -115,6 +132,8 @@ - (void)testJobIsActiveForTag { EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{}]; + self.queue.delegate = nil; // queue won't be able to complete task w/o delegate. so, we have time to check it + [self.queue enqueueJob:job]; XCTAssertFalse([self.queue jobIsActiveForTag:@"testTask"]); @@ -130,6 +149,8 @@ -(void)testNextJobForTag { EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTask" userInfo:@{@"testId":@"uniqueForThisTest"}]; + self.queue.delegate = nil; // queue won't be able to complete task w/o delegate. so, we have time to check it + [self.queue enqueueJob:job]; EDQueueJob *nextJob = [self.queue nextJobForTag:@"testTask"]; @@ -143,6 +164,8 @@ - (void)testIfQueuePersists { EDQueueJob *job = [[EDQueueJob alloc] initWithTag:@"testTaskUniqueName" userInfo:@{@"test":@"test"}]; + self.queue.delegate = nil; // queue won't be able to complete task w/o delegate. so, we have time to check it + [self.queue enqueueJob:job]; self.queue = nil; From 22aac2c7fdd74ecc07af9f731cd4f9122c930481 Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Tue, 1 Mar 2016 17:23:38 +0100 Subject: [PATCH 12/14] fixed issue with global dispatch queue (so, each instance if EDQueue will get it's own :) --- EDQueue/EDQueue.m | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index 81485e5..a16c672 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -23,7 +23,8 @@ @interface EDQueue () -@property (nonatomic, readwrite, nullable) NSString *activeJobTag; +@property (nonatomic, nullable) NSString *activeJobTag; +@property (nonatomic) dispatch_queue_t dispatchQueue; @end @@ -49,6 +50,7 @@ - (instancetype)initWithPersistentStore:(id)persistent self = [super init]; if (self) { _storage = persistentStore; + self.dispatchQueue = dispatch_queue_create("edqueue.serial", DISPATCH_QUEUE_SERIAL); } return self; } @@ -176,14 +178,7 @@ - (void)empty */ - (void)tick { - static dispatch_once_t onceToken; - static dispatch_queue_t gcd; - - dispatch_once(&onceToken, ^{ - gcd = dispatch_queue_create("edqueue.serial", DISPATCH_QUEUE_SERIAL); - }); - - dispatch_barrier_async(gcd, ^{ + dispatch_barrier_async(self.dispatchQueue, ^{ if (!self.isRunning) { return; From 29f231cb2ba9e83c16f1f6f21a98beee436149ac Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Mon, 7 Mar 2016 17:26:19 +0100 Subject: [PATCH 13/14] Interface refactoring: removed nullability from attempts property of EDQueueStorageItem protocol (and EDQueueStorageEngineJob respectively), blocked +new method from EDQueueJob ^^, --- EDQueue/EDQueue.m | 2 +- EDQueue/EDQueueJob.h | 1 + EDQueue/EDQueuePersistentStorageProtocol.h | 2 +- EDQueue/EDQueueStorageEngine.m | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/EDQueue/EDQueue.m b/EDQueue/EDQueue.m index a16c672..89e7818 100755 --- a/EDQueue/EDQueue.m +++ b/EDQueue/EDQueue.m @@ -1,4 +1,4 @@ - // +// // EDQueue.m // queue // diff --git a/EDQueue/EDQueueJob.h b/EDQueue/EDQueueJob.h index 21f621d..35db4de 100644 --- a/EDQueue/EDQueueJob.h +++ b/EDQueue/EDQueueJob.h @@ -26,6 +26,7 @@ static NSTimeInterval const EDQueueJobDefaultRetryTimeInterval = 15.0; userInfo:(nullable NSDictionary, id> *)userInfo; - (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; @end diff --git a/EDQueue/EDQueuePersistentStorageProtocol.h b/EDQueue/EDQueuePersistentStorageProtocol.h index 53589fc..5cfb2a5 100644 --- a/EDQueue/EDQueuePersistentStorageProtocol.h +++ b/EDQueue/EDQueuePersistentStorageProtocol.h @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) EDQueueJob *job; @property(nonatomic, readonly, nullable) NSNumber *jobID; -@property(nonatomic, readonly, nullable) NSNumber *attempts; +@property(nonatomic, readonly) NSNumber *attempts; @end diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index 2e98e54..9b9ec8f 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -35,7 +35,7 @@ @interface EDQueueStorageEngineJob : NSObject - (instancetype)initWithTag:(NSString *)tag userInfo:(nullable NSDictionary, id> *)userInfo jobID:(nullable NSNumber *)jobID - atempts:(nullable NSNumber *)attemps; + atempts:(NSNumber *)attemps; @end @@ -48,7 +48,7 @@ @implementation EDQueueStorageEngineJob - (instancetype)initWithTag:(NSString *)tag userInfo:(nullable NSDictionary, id> *)userInfo jobID:(nullable NSNumber *)jobID - atempts:(nullable NSNumber *)attemps + atempts:(NSNumber *)attemps { self = [super init]; From 7e2533530f614ea0a003224157ffcfd6e8a72414 Mon Sep 17 00:00:00 2001 From: Oleg Shanyuk Date: Tue, 5 Jul 2016 15:31:16 +0200 Subject: [PATCH 14/14] StorageEngine: use more powerful NSKeyedArchiver (allows to store your NSCoded objects) --- EDQueue.podspec | 4 ++-- EDQueue/EDQueueStorageEngine.m | 19 ++++++++++++++----- LICENSE.md | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/EDQueue.podspec b/EDQueue.podspec index 791da12..908f312 100644 --- a/EDQueue.podspec +++ b/EDQueue.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |s| s.name = 'EDQueue' - s.version = '1.0' + s.version = '1.1' s.license = 'MIT' s.summary = 'A persistent background job queue for iOS.' s.homepage = 'https://github.com/gelosi/queue' s.authors = {'Andrew Sliwinski' => 'andrewsliwinski@acm.org', 'Francois Lambert' => 'flambert@mirego.com', 'Oleg Shanyuk' => 'oleg.shanyuk@gmail.com'} - s.source = { :git => 'https://github.com/gelosi/queue.git', :tag => 'v1.0' } + s.source = { :git => 'https://github.com/gelosi/queue.git', :tag => 'v1.1' } s.platform = :ios, '7.0' s.source_files = 'EDQueue' s.library = 'sqlite3.0' diff --git a/EDQueue/EDQueueStorageEngine.m b/EDQueue/EDQueueStorageEngine.m index 9b9ec8f..58e5fe5 100755 --- a/EDQueue/EDQueueStorageEngine.m +++ b/EDQueue/EDQueueStorageEngine.m @@ -125,11 +125,9 @@ - (void)createJob:(EDQueueJob *)job @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"EDQueueJob.userInfo can not be nil" userInfo:nil]; } - NSData *data = [NSJSONSerialization dataWithJSONObject:job.userInfo - options:NSJSONWritingPrettyPrinted - error:nil]; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:job.userInfo]; - NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSString *dataString = [data base64EncodedStringWithOptions:0]; [self.queue inDatabase:^(FMDatabase *db) { @@ -332,7 +330,18 @@ - (NSTimeInterval)fetchNextJobTimeInterval - (id)_jobFromResultSet:(FMResultSet *)rs { - NSDictionary *userInfo = [NSJSONSerialization JSONObjectWithData:[[rs stringForColumn:@"data"] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil]; + NSString *encodedString = [rs stringForColumn:@"data"]; + NSDictionary *userInfo; + @try { + NSData *data = [[NSData alloc] initWithBase64EncodedString:encodedString options:0]; + if (encodedString && !data) { + @throw [NSException exceptionWithName:NSInvalidArchiveOperationException reason:nil userInfo:nil]; + } + userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + } @catch (NSException *exception) { + /* respect previous JSON serialization [though, good idea to move serializer out of storage] */ + userInfo = [NSJSONSerialization JSONObjectWithData:[encodedString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil]; + } EDQueueStorageEngineJob *storedItem = [[EDQueueStorageEngineJob alloc] initWithTag:[rs stringForColumn:@"tag"] userInfo:userInfo diff --git a/LICENSE.md b/LICENSE.md index 1226c79..9b005d4 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2012 Andrew Sliwinski +Copyright (c) 2012 Andrew Sliwinski, 2016 Oleg Shanyuk 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: