Various improvements in style and functionality

Fixed crash on vivify error.
Cleanup for names and properties.
Reformatted to webkit style.
Better treatment of logging network failures, actual done text when done.
Don't double add uploads
Moved the settings panel showing to viewdidappear, where it should be.
Added images to the upload cells.
Reset progress bar for reusable cells.
Updated podfile with keychain tools.
Updated for better keychain management.
Mitigated a leak.

Change-Id: Iad428aae56ac26e71743bd3e86b0f7736eff9767
This commit is contained in:
Nick O'Neill 2014-01-16 13:29:55 -08:00
parent 7c805515bc
commit 12e449564d
22 changed files with 645 additions and 447 deletions

View File

@ -1,3 +1,4 @@
platform :ios, '7.0'
pod 'HockeySDK', '~> 3.5.0'
pod 'HockeySDK', '~> 3.5.0'
pod 'SSKeychain', '~> 1.2.1'

View File

@ -1,10 +1,13 @@
PODS:
- HockeySDK (3.5.0)
- HockeySDK (3.5.1)
- SSKeychain (1.2.1)
DEPENDENCIES:
- HockeySDK (~> 3.5.0)
- SSKeychain (~> 1.2.1)
SPEC CHECKSUMS:
HockeySDK: 685e6ca0b78668ef8b595fba17fe087ea6074b05
HockeySDK: 7dfbfa6cdbae199d66eef638e9682b93b00acb95
SSKeychain: d18926838c2e7cd342e2a49e9f869858e49f035a
COCOAPODS: 0.29.0

View File

@ -1,3 +1,5 @@
== SETUP
Use the podfile to setup the (ignored) dependencies, `pod install` should be all you need, then open the .xcworkspace file in xcode.
Use the podfile to setup the (ignored) dependencies, `pod install` should be all you need, then open the .xcworkspace file in xcode.
We use clang-format in the form of the ClangFormat-Xcode plugin (https://github.com/travisjeffery/ClangFormat-Xcode) for style consistency. Please set your formatting tool to use the WebKit style (http://www.webkit.org/coding/coding-style.html).

View File

@ -13,17 +13,18 @@
@class ALAssetsLibrary;
static NSString *const CamliUsernameKey = @"org.camlistore.username";
static NSString *const CamliServerKey = @"org.camlistore.serverurl";
static NSString* const CamliUsernameKey = @"org.camlistore.username";
static NSString* const CamliServerKey = @"org.camlistore.serverurl";
static NSString* const CamliCredentialsKey = @"org.camlistore.credentials";
@interface LAAppDelegate : UIResponder <UIApplicationDelegate,CLLocationManagerDelegate,BITHockeyManagerDelegate>
@interface LAAppDelegate : UIResponder <UIApplicationDelegate, CLLocationManagerDelegate, BITHockeyManagerDelegate>
@property (strong, nonatomic) UIWindow *window;
@property CLLocationManager *locationManager;
@property(strong, nonatomic) UIWindow* window;
@property CLLocationManager* locationManager;
@property LACamliClient *client;
@property LACamliClient* client;
// kicked out of the library if we don't have a reference and still want to play with the books
@property ALAssetsLibrary *library;
@property ALAssetsLibrary* library;
- (void)loadCredentials;
- (void)checkForUploads;

View File

@ -13,10 +13,9 @@
#import <AssetsLibrary/AssetsLibrary.h>
#import <HockeySDK/HockeySDK.h>
@implementation LAAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"de94cf9f0f0ad2ea0b19b2ad18ebe11f"
delegate:self];
@ -35,40 +34,39 @@
return YES;
}
- (NSString *)customDeviceIdentifierForUpdateManager:(BITUpdateManager *)updateManager
{
if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)]) {
return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
}
return nil;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
- (void)locationManager:(CLLocationManager*)manager didUpdateLocations:(NSArray*)locations
{
[self checkForUploads];
}
- (void)loadCredentials
{
NSURL *serverURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] stringForKey:CamliServerKey]];
NSString *username = [[NSUserDefaults standardUserDefaults] stringForKey:CamliUsernameKey];
NSURL* serverURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] stringForKey:CamliServerKey]];
NSString* username = [[NSUserDefaults standardUserDefaults] stringForKey:CamliUsernameKey];
NSString *password = nil;
NSString* password = nil;
if (username) {
password = [LACamliUtil passwordForUsername:username];
}
if (serverURL && username && password) {
[LACamliUtil statusText:@[@"found credentials"]];
[LACamliUtil logText:@[@"found credentials"]];
self.client = [[LACamliClient alloc] initWithServer:serverURL username:username andPassword:password];
[LACamliUtil statusText:@[
@"found credentials"
]];
[LACamliUtil logText:@[
@"found credentials"
]];
self.client = [[LACamliClient alloc] initWithServer:serverURL
username:username
andPassword:password];
// TODO there must be a better way to get the current instance of this
LAViewController *mainView = (LAViewController *)[(UINavigationController *)self.window.rootViewController topViewController];
LAViewController* mainView = (LAViewController*)[(UINavigationController*)self.window.rootViewController topViewController];
[self.client setDelegate:mainView];
} else {
[LACamliUtil statusText:@[@"credentials or server not found"]];
[LACamliUtil statusText:@[
@"credentials or server not found"
]];
}
[self checkForUploads];
@ -79,7 +77,9 @@
if (self.client && [self.client readyToUpload]) {
NSInteger __block filesToUpload = 0;
[LACamliUtil statusText:@[@"looking for new files..."]];
[LACamliUtil statusText:@[
@"looking for new files..."
]];
// checking all assets can take some time
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@ -114,30 +114,30 @@
}
}
- (void)applicationWillResignActive:(UIApplication *)application
- (void)applicationWillResignActive:(UIApplication*)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
- (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.
// 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.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
- (void)applicationWillEnterForeground:(UIApplication*)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
- (void)applicationDidBecomeActive:(UIApplication*)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
[self checkForUploads];
}
- (void)applicationWillTerminate:(UIApplication *)application
- (void)applicationWillTerminate:(UIApplication*)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

View File

@ -7,48 +7,48 @@
#import <Foundation/Foundation.h>
@class LACamliFile,LACamliUploadOperation;
@class LACamliFile, LACamliUploadOperation;
@protocol LACamliStatusDelegate <NSObject>
@optional
- (void)updatedStatus:(NSString *)status;
- (void)addedUploadOperation:(LACamliUploadOperation *)op;
- (void)finishedUploadOperation:(LACamliUploadOperation *)op;
- (void)uploadProgress:(float)pct forOperation:(LACamliUploadOperation *)op;
- (void)updatedStatus:(NSString*)status;
- (void)addedUploadOperation:(LACamliUploadOperation*)op;
- (void)finishedUploadOperation:(LACamliUploadOperation*)op;
- (void)uploadProgress:(float)pct forOperation:(LACamliUploadOperation*)op;
@end
@interface LACamliClient : NSObject <NSURLSessionDelegate>
extern NSString *const CamliNotificationUploadStart;
extern NSString *const CamliNotificationUploadProgress;
extern NSString *const CamliNotificationUploadEnd;
extern NSString* const CamliNotificationUploadStart;
extern NSString* const CamliNotificationUploadProgress;
extern NSString* const CamliNotificationUploadEnd;
@property NSURLSessionConfiguration *sessionConfig;
@property NSURLSessionConfiguration* sessionConfig;
@property id delegate;
@property NSURL *serverURL;
@property NSString *username;
@property NSString *password;
@property NSURL* serverURL;
@property NSString* username;
@property NSString* password;
@property NSString *blobRootComponent;
@property NSOperationQueue *uploadQueue;
@property NSString* blobRootComponent;
@property NSOperationQueue* uploadQueue;
@property NSUInteger totalUploads;
@property NSMutableArray *uploadedBlobRefs;
@property NSMutableArray* uploadedBlobRefs;
@property UIBackgroundTaskIdentifier backgroundID;
@property BOOL isAuthorized;
@property BOOL authorizing;
- (id)initWithServer:(NSURL *)server username:(NSString *)username andPassword:(NSString *)password;
- (id)initWithServer:(NSURL*)server username:(NSString*)username andPassword:(NSString*)password;
- (BOOL)readyToUpload;
- (void)discoveryWithUsername:(NSString *)user andPassword:(NSString *)pass;
- (void)discoveryWithUsername:(NSString*)user andPassword:(NSString*)pass;
- (BOOL)fileAlreadyUploaded:(LACamliFile *)file;
- (void)addFile:(LACamliFile *)file withCompletion:(void (^)())completion;
- (BOOL)fileAlreadyUploaded:(LACamliFile*)file;
- (void)addFile:(LACamliFile*)file withCompletion:(void (^)())completion;
- (NSURL *)statUrl;
- (NSURL *)uploadUrl;
- (NSURL*)statURL;
- (NSURL*)uploadURL;
@end

View File

@ -12,40 +12,53 @@
@implementation LACamliClient
NSString *const CamliNotificationUploadStart = @"camli-upload-start";
NSString *const CamliNotificationUploadProgress = @"camli-upload-progress";
NSString *const CamliNotificationUploadEnd = @"camli-upload-end";
NSString* const CamliNotificationUploadStart = @"camli-upload-start";
NSString* const CamliNotificationUploadProgress = @"camli-upload-progress";
NSString* const CamliNotificationUploadEnd = @"camli-upload-end";
- (id)initWithServer:(NSURL *)server username:(NSString *)username andPassword:(NSString *)password
- (id)initWithServer:(NSURL*)server
username:(NSString*)username
andPassword:(NSString*)password
{
NSParameterAssert(server);
NSParameterAssert(username);
NSParameterAssert(password);
if (self = [super init]) {
self.serverURL = server;
self.username = username;
self.password = password;
if ([[NSFileManager defaultManager] fileExistsAtPath:[self uploadedBlobRefArchivePath]]) {
self.uploadedBlobRefs = [NSMutableArray arrayWithContentsOfFile:[self uploadedBlobRefArchivePath]];
}
if (!self.uploadedBlobRefs) {
self.uploadedBlobRefs = [NSMutableArray array];
}
[LACamliUtil logText:@[@"uploads in cache: ",[NSString stringWithFormat:@"%d",[_uploadedBlobRefs count]]]];
self.uploadQueue = [[NSOperationQueue alloc] init];
self.uploadQueue.maxConcurrentOperationCount = 1;
self.totalUploads = 0;
_serverURL = server;
_username = username;
_password = password;
if ([[NSFileManager defaultManager]
fileExistsAtPath:[self uploadedBlobRefArchivePath]]) {
_uploadedBlobRefs = [NSMutableArray
arrayWithContentsOfFile:[self uploadedBlobRefArchivePath]];
}
if (!_uploadedBlobRefs) {
_uploadedBlobRefs = [NSMutableArray array];
}
[LACamliUtil logText:@[
@"uploads in cache: ",
[NSString stringWithFormat:@"%lu", (unsigned long)
[_uploadedBlobRefs count]]
]];
_uploadQueue = [[NSOperationQueue alloc] init];
_uploadQueue.maxConcurrentOperationCount = 1;
_totalUploads = 0;
_isAuthorized = false;
_authorizing = false;
self.isAuthorized = false;
self.authorizing = false;
_sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
_sessionConfig.HTTPAdditionalHeaders = @{@"Authorization": [NSString stringWithFormat:@"Basic %@",[self encodedAuth]]};
_sessionConfig.HTTPAdditionalHeaders = @{
@"Authorization" :
[NSString stringWithFormat:@"Basic %@", [self encodedAuth]]
};
}
return self;
}
@ -54,63 +67,126 @@ NSString *const CamliNotificationUploadEnd = @"camli-upload-end";
- (BOOL)readyToUpload
{
// can't upload if we don't have credentials
if (!self.username || !self.password || !self.serverURL) {
[LACamliUtil logText:@[@"not ready: no u/p/s"]];
if (!_username || !_password || !_serverURL) {
[LACamliUtil logText:@[
@"not ready: no u/p/s"
]];
return NO;
}
// don't want to start a new upload if we're already going
if ([self.uploadQueue operationCount] > 0) {
[LACamliUtil logText:@[@"not ready: already uploading"]];
if ([_uploadQueue operationCount] > 0) {
[LACamliUtil logText:@[
@"not ready: already uploading"
]];
return NO;
}
[LACamliUtil logText:@[@"starting upload"]];
[LACamliUtil logText:@[
@"starting upload"
]];
return YES;
}
#pragma mark - discovery
- (void)discoveryWithUsername:(NSString *)user andPassword:(NSString *)pass
- (void)discoveryWithUsername:(NSString*)user andPassword:(NSString*)pass
{
[LACamliUtil statusText:@[@"discovering..."]];
self.authorizing = YES;
[LACamliUtil statusText:@[
@"discovering..."
]];
_authorizing = YES;
NSURLSessionConfiguration *discoverConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
discoverConfig.HTTPAdditionalHeaders = @{@"Accept": @"text/x-camli-configuration", @"Authorization": [NSString stringWithFormat:@"Basic %@",[self encodedAuth]]};
NSURLSession *discoverSession = [NSURLSession sessionWithConfiguration:discoverConfig delegate:self delegateQueue:nil];
NSURLSessionConfiguration* discoverConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
discoverConfig.HTTPAdditionalHeaders = @{
@"Accept" : @"text/x-camli-configuration",
@"Authorization" :
[NSString stringWithFormat:@"Basic %@", [self encodedAuth]]
};
NSURLSession* discoverSession =
[NSURLSession sessionWithConfiguration:discoverConfig
delegate:self
delegateQueue:nil];
NSURLSessionDataTask *data = [discoverSession dataTaskWithURL:self.serverURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSURLSessionDataTask *data = [discoverSession dataTaskWithURL:_serverURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if (error) {
LALog(@"error discovery: %@",error);
[LACamliUtil errorText:@[@"discovery error: ",[error description]]];
if ([error code] == NSURLErrorNotConnectedToInternet || [error code] == NSURLErrorNetworkConnectionLost) {
LALog(@"connection lost or unavailable");
[LACamliUtil statusText:@[
@"internet connection appears offline"
]];
} else {
LALog(@"error discovery: %@", error);
[LACamliUtil errorText:@[
@"discovery error: ",
[error description]
]];
}
} else {
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
NSHTTPURLResponse* res = (NSHTTPURLResponse*)response;
if (res.statusCode != 200) {
LALog(@"error with discovery: %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
[LACamliUtil errorText:@[@"error discovery: ",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]];
[LACamliUtil logText:@[[NSString stringWithFormat:@"server said: %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]]];
LALog(@"error with discovery: %@",
[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding]);
[LACamliUtil
errorText:@[
@"error discovery: ",
[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding]
]];
[LACamliUtil
logText:@[
[NSString stringWithFormat:
@"server said: %@",
[[NSString alloc]
initWithData:data
encoding:NSUTF8StringEncoding]]
]];
[[NSNotificationCenter defaultCenter] postNotificationName:CamliNotificationUploadEnd object:nil];
[[NSNotificationCenter defaultCenter]
postNotificationName:CamliNotificationUploadEnd
object:nil];
} else {
NSError *err;
NSDictionary *config = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];
NSError* err;
NSDictionary* config = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&err];
if (!err) {
self.blobRootComponent = config[@"blobRoot"];
[LACamliUtil logText:@[[NSString stringWithFormat:@"Welcome to %@'s camlistore",config[@"ownerName"]]]];
_blobRootComponent = config[@"blobRoot"];
[LACamliUtil
logText:
@[
[NSString stringWithFormat:@"Welcome to %@'s camlistore",
config[@"ownerName"]]
]];
self.isAuthorized = YES;
[self.uploadQueue setSuspended:NO];
_isAuthorized = YES;
[_uploadQueue setSuspended:NO];
LALog(@"good discovery: %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
[LACamliUtil statusText:@[@"discovery OK"]];
LALog(@"good discovery: %@",
[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding]);
[LACamliUtil statusText:@[
@"discovery OK"
]];
} else {
LALog(@"couldn't deserialize discovery json");
[LACamliUtil errorText:@[@"bad json from discovery", [err description]]];
[LACamliUtil logText:@[@"json from discovery: ",[err description]]];
[LACamliUtil
errorText:@[
@"bad json from discovery",
[err description]
]];
[LACamliUtil
logText:@[
@"json from discovery: ",
[err description]
]];
}
}
}
@ -121,91 +197,111 @@ NSString *const CamliNotificationUploadEnd = @"camli-upload-end";
#pragma mark - upload methods
- (BOOL)fileAlreadyUploaded:(LACamliFile *)file
- (BOOL)fileAlreadyUploaded:(LACamliFile*)file
{
NSParameterAssert(file);
if ([self.uploadedBlobRefs containsObject:file.blobRef]) {
if ([_uploadedBlobRefs containsObject:file.blobRef]) {
return YES;
}
// also check to make sure it's not in the queue waiting
for (LACamliUploadOperation* op in [_uploadQueue operations]) {
if ([op.file.blobRef isEqualToString:file.blobRef]) {
return YES;
}
}
return NO;
}
// starts uploading immediately
- (void)addFile:(LACamliFile *)file withCompletion:(void (^)())completion
- (void)addFile:(LACamliFile*)file withCompletion:(void (^)())completion
{
NSParameterAssert(file);
self.totalUploads++;
_totalUploads++;
if (![self isAuthorized]) {
[self.uploadQueue setSuspended:YES];
[_uploadQueue setSuspended:YES];
if (!self.authorizing) {
[self discoveryWithUsername:self.username andPassword:self.password];
if (!_authorizing) {
[self discoveryWithUsername:_username
andPassword:_password];
}
}
LACamliUploadOperation *op = [[LACamliUploadOperation alloc] initWithFile:file andClient:self];
__block LACamliUploadOperation *weakOp = op;
LACamliUploadOperation* op =
[[LACamliUploadOperation alloc] initWithFile:file
andClient:self];
__block LACamliUploadOperation* weakOp = op;
op.completionBlock = ^{
LALog(@"finished op %@",file.blobRef);
LALog(@"finished op %@", file.blobRef);
if ([_delegate respondsToSelector:@selector(finishedUploadOperation:)]) {
[_delegate performSelector:@selector(finishedUploadOperation:) onThread:[NSThread mainThread] withObject:weakOp waitUntilDone:NO];
[_delegate performSelector:@selector(finishedUploadOperation:)
onThread:[NSThread mainThread]
withObject:weakOp
waitUntilDone:NO];
}
if (weakOp.failedTransfer) {
LALog(@"failed transfer");
} else {
[self.uploadedBlobRefs addObject:file.blobRef];
[self.uploadedBlobRefs writeToFile:[self uploadedBlobRefArchivePath] atomically:YES];
[_uploadedBlobRefs addObject:file.blobRef];
[_uploadedBlobRefs writeToFile:[self uploadedBlobRefArchivePath]
atomically:YES];
}
weakOp = nil;
if (![self.uploadQueue operationCount]) {
self.totalUploads = 0;
if (![_uploadQueue operationCount]) {
_totalUploads = 0;
[LACamliUtil statusText:@[@"done uploading"]];
}
if (completion) {
completion();
}
};
if ([_delegate respondsToSelector:@selector(addedUploadOperation:)]) {
[_delegate performSelector:@selector(addedUploadOperation:) onThread:[NSThread mainThread] withObject:weakOp waitUntilDone:NO];
[_delegate performSelector:@selector(addedUploadOperation:)
onThread:[NSThread mainThread]
withObject:op
waitUntilDone:NO];
}
[self.uploadQueue addOperation:op];
[_uploadQueue addOperation:op];
}
#pragma mark - utility
- (NSURL *)blobRoot
- (NSURL*)blobRoot
{
return [self.serverURL URLByAppendingPathComponent:self.blobRootComponent];
return [_serverURL URLByAppendingPathComponent:_blobRootComponent];
}
- (NSURL *)statUrl
- (NSURL*)statURL
{
return [[self blobRoot] URLByAppendingPathComponent:@"camli/stat"];
}
- (NSURL *)uploadUrl
- (NSURL*)uploadURL
{
return [[self blobRoot] URLByAppendingPathComponent:@"camli/upload"];
}
- (NSString *)encodedAuth
- (NSString*)encodedAuth
{
NSString *auth = [NSString stringWithFormat:@"%@:%@",self.username,self.password];
NSString* auth = [NSString stringWithFormat:@"%@:%@", _username, _password];
return [LACamliUtil base64EncodedStringFromString:auth];
}
- (NSString *)uploadedBlobRefArchivePath
- (NSString*)uploadedBlobRefArchivePath
{
NSString *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString* documents = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES)[0];
return [documents stringByAppendingPathComponent:@"uploadedRefs.plist"];
}

View File

@ -11,17 +11,18 @@
@interface LACamliFile : NSObject
@property ALAsset *asset;
@property NSMutableArray *allBlobs;
@property NSMutableArray *uploadMarks;
@property NSArray *allBlobRefs;
@property ALAsset* asset;
@property NSMutableArray* allBlobs;
@property NSMutableArray* uploadMarks;
@property NSArray* allBlobRefs;
@property NSString *blobRef;
@property NSString* blobRef;
- (id)initWithAsset:(ALAsset *)asset;
- (NSArray *)blobsToUpload;
- (id)initWithAsset:(ALAsset*)asset;
- (NSArray*)blobsToUpload;
- (long long)size;
- (NSDate *)creation;
- (NSDate*)creation;
- (UIImage*)thumbnail;
@end

View File

@ -16,82 +16,92 @@
static NSUInteger const ChunkSize = 64000;
- (id)initWithAsset:(ALAsset *)asset
- (id)initWithAsset:(ALAsset*)asset
{
if (self = [super init]) {
self.asset = asset;
_asset = asset;
[self setBlobRef:[LACamliUtil blobRef:[self fileData]]];
float chunkCount = (float)[self size] / (float)ChunkSize;
self.uploadMarks = [NSMutableArray array];
_uploadMarks = [NSMutableArray array];
for (int i = 0; i < chunkCount; i++) {
[self.uploadMarks addObject:@YES];
[_uploadMarks addObject:@YES];
}
}
return self;
}
- (id)initWithPath:(NSString *)path
- (id)initWithPath:(NSString*)path
{
// TODO, can init from random path to file
if (self = [super init]) {
// [self setBlobRef:[LACamliClient blobRef:data]];
// [self setFileData:data];
// [self setBlobRef:[LACamliClient blobRef:data]];
// [self setFileData:data];
// set time, size and other properties here?
}
return self;
}
#pragma mark - convenience
- (NSData *)fileData
- (NSData*)fileData
{
ALAssetRepresentation *rep = [self.asset defaultRepresentation];
Byte *buf = (Byte*)malloc((int)rep.size);
NSUInteger bufferLength = [rep getBytes:buf fromOffset:0.0 length:(int)rep.size error:nil];
return [NSData dataWithBytesNoCopy:buf length:bufferLength freeWhenDone:YES];
ALAssetRepresentation* rep = [_asset defaultRepresentation];
Byte* buf = (Byte*)malloc((int)rep.size);
NSUInteger bufferLength = [rep getBytes:buf
fromOffset:0.0
length:(int)rep.size
error:nil];
return [NSData dataWithBytesNoCopy:buf
length:bufferLength
freeWhenDone:YES];
}
- (long long)size
{
return [self.asset defaultRepresentation].size;
return [_asset defaultRepresentation].size;
}
- (NSDate *)creation
- (NSDate*)creation
{
return [self.asset valueForProperty:ALAssetPropertyDate];
return [_asset valueForProperty:ALAssetPropertyDate];
}
- (NSArray *)blobsToUpload
- (UIImage*)thumbnail
{
NSMutableArray *blobs = [NSMutableArray array];
return [UIImage imageWithCGImage:[_asset thumbnail]];
}
- (NSArray*)blobsToUpload
{
NSMutableArray* blobs = [NSMutableArray array];
int i = 0;
for (NSData *blob in self.allBlobs) {
if ([[self.uploadMarks objectAtIndex:i] boolValue]) {
for (NSData* blob in _allBlobs) {
if ([[_uploadMarks objectAtIndex:i] boolValue]) {
[blobs addObject:blob];
}
i++;
}
return blobs;
}
#pragma mark - delayed creation methods
- (void)setAllBlobs:(NSMutableArray *)allBlobs
- (void)setAllBlobs:(NSMutableArray*)allBlobs
{
_allBlobs = allBlobs;
}
- (NSMutableArray *)allBlobs
- (NSMutableArray*)allBlobs
{
if (!_allBlobs) {
[self makeBlobsAndRefs];
@ -101,49 +111,48 @@ static NSUInteger const ChunkSize = 64000;
return _allBlobs;
}
- (void)setAllBlobRefs:(NSArray *)allBlobRefs
- (void)setAllBlobRefs:(NSArray*)allBlobRefs
{
_allBlobRefs = allBlobRefs;
}
- (NSArray *)allBlobRefs
- (NSArray*)allBlobRefs
{
if (!_allBlobRefs) {
[self makeBlobsAndRefs];
}
// not a huge fan of how this doesn't obviously assign to _allBlobRefs
return _allBlobRefs;
}
- (void)makeBlobsAndRefs
{
LALog(@"making blob refs");
NSMutableArray *chunks = [NSMutableArray array];
NSMutableArray *blobRefs = [NSMutableArray array];
float chunkCount = (float)self.size / (float)ChunkSize;
NSMutableArray* chunks = [NSMutableArray array];
NSMutableArray* blobRefs = [NSMutableArray array];
float chunkCount = (float)[self size] / (float)ChunkSize;
NSData* fileData = [self fileData];
NSData *fileData = [self fileData];
for (int i = 0; i < chunkCount; i++) {
// ChunkSize size chunks, unless the last one is less
NSData *chunkData;
if (ChunkSize*(i+1) <= [self size]) {
chunkData = [fileData subdataWithRange:NSMakeRange(ChunkSize*i, ChunkSize)];
NSData* chunkData;
if (ChunkSize * (i + 1) <= [self size]) {
chunkData = [fileData subdataWithRange:NSMakeRange(ChunkSize * i, ChunkSize)];
} else {
chunkData = [fileData subdataWithRange:NSMakeRange(ChunkSize*i, (int)[self size]-(ChunkSize*i))];
chunkData = [fileData subdataWithRange:NSMakeRange(ChunkSize * i, (int)[self size] - (ChunkSize * i))];
}
[chunks addObject:chunkData];
[blobRefs addObject:[LACamliUtil blobRef:chunkData]];
}
self.allBlobs = chunks;
self.allBlobRefs = blobRefs;
_allBlobs = chunks;
_allBlobRefs = blobRefs;
}
@end

View File

@ -8,22 +8,22 @@
#import <Foundation/Foundation.h>
@class LACamliFile,LACamliClient;
@class LACamliFile, LACamliClient;
@interface LACamliUploadOperation : NSOperation <NSURLSessionDelegate>
@property LACamliClient *client;
@property LACamliFile *file;
@property NSURLSession *session;
@property LACamliClient* client;
@property LACamliFile* file;
@property NSURLSession* session;
@property UIBackgroundTaskIdentifier taskID;
@property (readonly) BOOL failedTransfer;
@property (readonly) BOOL isExecuting;
@property (readonly) BOOL isFinished;
@property(readonly) BOOL failedTransfer;
@property(readonly) BOOL isExecuting;
@property(readonly) BOOL isFinished;
- (id)initWithFile:(LACamliFile *)file andClient:(LACamliClient *)client;
- (id)initWithFile:(LACamliFile*)file andClient:(LACamliClient*)client;
- (BOOL)isConcurrent;
- (NSString *)name;
- (NSString*)name;
@end

View File

@ -12,11 +12,11 @@
#import "LACamliUtil.h"
static NSUInteger const camliVersion = 1;
static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
static NSString* const multipartBoundary = @"Qe43VdbVVaGtkkMd";
@implementation LACamliUploadOperation
- (id)initWithFile:(LACamliFile *)file andClient:(LACamliClient *)client
- (id)initWithFile:(LACamliFile*)file andClient:(LACamliClient*)client
{
NSParameterAssert(file);
NSParameterAssert(client);
@ -27,7 +27,9 @@ static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
_isExecuting = NO;
_isFinished = NO;
_failedTransfer = NO;
_session = [NSURLSession sessionWithConfiguration:_client.sessionConfig delegate:self delegateQueue:nil];
_session = [NSURLSession sessionWithConfiguration:_client.sessionConfig
delegate:self
delegateQueue:nil];
}
return self;
@ -38,19 +40,26 @@ static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
return YES;
}
- (NSString *)name
#pragma mark - convenience
- (NSString*)name
{
return _file.blobRef;
}
#pragma mark - operation flow
// request stats for each chunk, making sure the server doesn't already have the chunk
- (void)start
{
[LACamliUtil statusText:@[@"performing stat..."]];
[LACamliUtil statusText:@[
@"performing stat..."
]];
_taskID = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"uploadtask" expirationHandler:^{
_taskID = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"uploadtask"
expirationHandler:^{
LALog(@"upload task expired");
}];
}];
if (_client.backgroundID) {
[[UIApplication sharedApplication] endBackgroundTask:_client.backgroundID];
@ -59,68 +68,89 @@ static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[NSNumber numberWithInt:camliVersion] forKey:@"camliversion"];
NSMutableDictionary* params = [NSMutableDictionary dictionary];
[params setObject:[NSNumber numberWithInt:camliVersion]
forKey:@"camliversion"];
int i = 1;
for (NSString *blobRef in _file.allBlobRefs) {
[params setObject:blobRef forKey:[NSString stringWithFormat:@"blob%d",i]];
for (NSString* blobRef in _file.allBlobRefs) {
[params setObject:blobRef
forKey:[NSString stringWithFormat:@"blob%d", i]];
i++;
}
NSString *formValues = @"";
for (NSString *key in params) {
formValues = [formValues stringByAppendingString:[NSString stringWithFormat:@"%@=%@&",key,params[key]]];
NSString* formValues = @"";
for (NSString* key in params) {
formValues = [formValues stringByAppendingString:[NSString stringWithFormat:@"%@=%@&", key, params[key]]];
}
LALog(@"uploading to %@",[_client statUrl]);
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[_client statUrl]];
LALog(@"uploading to %@", [_client statURL]);
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:[_client statURL]];
[req setHTTPMethod:@"POST"];
[req setHTTPBody:[formValues dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *statTask = [_session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSURLSessionDataTask *statTask = [_session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if (!error) {
// LALog(@"data: %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
// LALog(@"data: %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
// we can remove any chunks that the server claims it already has
NSError *err;
NSMutableDictionary *resObj = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];
NSError* err;
NSMutableDictionary* resObj = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&err];
if (err) {
LALog(@"error getting json: %@",err);
LALog(@"error getting json: %@", err);
}
if (resObj[@"stat"] != [NSNull null]) {
for (NSDictionary *stat in resObj[@"stat"]) {
for (NSString *blobRef in _file.allBlobRefs) {
for (NSDictionary* stat in resObj[@"stat"]) {
for (NSString* blobRef in _file.allBlobRefs) {
if ([stat[@"blobRef"] isEqualToString:blobRef]) {
[_file.uploadMarks replaceObjectAtIndex:[_file.allBlobRefs indexOfObject:blobRef] withObject:@NO];
[_file.uploadMarks replaceObjectAtIndex:[_file.allBlobRefs indexOfObject:blobRef]
withObject:@NO];
}
}
}
}
BOOL allUploaded = YES;
for (NSNumber *upload in _file.uploadMarks) {
for (NSNumber* upload in _file.uploadMarks) {
if ([upload boolValue]) {
allUploaded = NO;
}
}
// TODO: there's a posibility all chunks have been uploaded but no permanode exists
if (allUploaded) {
LALog(@"everything's been uploaded already for this file");
[LACamliUtil logText:@[@"everything already uploaded for ", _file.blobRef]];
[LACamliUtil logText:@[
@"everything already uploaded for ",
_file.blobRef
]];
[self finished];
return;
}
[self uploadChunks];
} else {
LALog(@"failed stat: %@",error);
[LACamliUtil errorText:@[@"failed to stat: ",[error description]]];
[LACamliUtil logText:@[[NSString stringWithFormat:@"failed to stat: %@",error]]];
if ([error code] == NSURLErrorNotConnectedToInternet || [error code] == NSURLErrorNetworkConnectionLost) {
LALog(@"connection lost or unavailable");
[LACamliUtil statusText:@[
@"internet connection appears offline"
]];
} else {
LALog(@"failed stat: %@", error);
[LACamliUtil errorText:@[
@"failed to stat: ",
[error description]
]];
[LACamliUtil logText:@[
[NSString stringWithFormat:@"failed to stat: %@", error]
]];
}
_failedTransfer = YES;
[self finished];
@ -130,25 +160,37 @@ static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
[statTask resume];
}
// post the chunks in a multipart request
//
- (void)uploadChunks
{
[LACamliUtil statusText:@[@"uploading..."]];
[LACamliUtil statusText:@[
@"uploading..."
]];
NSMutableURLRequest *uploadReq = [NSMutableURLRequest requestWithURL:[_client uploadUrl]];
NSMutableURLRequest* uploadReq = [NSMutableURLRequest requestWithURL:[_client uploadURL]];
[uploadReq setHTTPMethod:@"POST"];
[uploadReq setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", multipartBoundary] forHTTPHeaderField:@"Content-Type"];
[uploadReq setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", multipartBoundary]
forHTTPHeaderField:@"Content-Type"];
NSMutableData *uploadData = [self multipartDataForChunks];
NSMutableData* uploadData = [self multipartDataForChunks];
NSURLSessionUploadTask *upload = [_session uploadTaskWithRequest:uploadReq fromData:uploadData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSURLSessionUploadTask *upload = [_session uploadTaskWithRequest:uploadReq fromData:uploadData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
// LALog(@"upload response: %@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
// LALog(@"upload response: %@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
if (error) {
LALog(@"upload error: %@",error);
[LACamliUtil errorText:@[@"error uploading: ",error]];
if ([error code] == NSURLErrorNotConnectedToInternet || [error code] == NSURLErrorNetworkConnectionLost) {
LALog(@"connection lost or unavailable");
[LACamliUtil statusText:@[
@"internet connection appears offline"
]];
} else {
LALog(@"upload error: %@", error);
[LACamliUtil errorText:@[
@"error uploading: ",
error
]];
}
_failedTransfer = YES;
[self finished];
} else {
@ -159,33 +201,30 @@ static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
[upload resume];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
if ([_client.delegate respondsToSelector:@selector(uploadProgress:forOperation:)]) {
float progress = (float)totalBytesSent/(float)totalBytesExpectedToSend;
dispatch_async(dispatch_get_main_queue(), ^{
[_client.delegate uploadProgress:progress forOperation:self];
});
}
}
// ask the server to vivify the blobrefs into a file
- (void)vivifyChunks
{
[LACamliUtil statusText:@[@"vivify"]];
[LACamliUtil statusText:@[
@"vivify"
]];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[_client uploadUrl]];
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:[_client uploadURL]];
[req setHTTPMethod:@"POST"];
[req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", multipartBoundary] forHTTPHeaderField:@"Content-Type"];
[req addValue:@"1" forHTTPHeaderField:@"X-Camlistore-Vivify"];
[req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", multipartBoundary]
forHTTPHeaderField:@"Content-Type"];
[req addValue:@"1"
forHTTPHeaderField:@"X-Camlistore-Vivify"];
NSMutableData *vivifyData = [self multipartVivifyDataForChunks];
NSMutableData* vivifyData = [self multipartVivifyDataForChunks];
NSURLSessionUploadTask *vivify = [_session uploadTaskWithRequest:req fromData:vivifyData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSURLSessionUploadTask *vivify = [_session uploadTaskWithRequest:req fromData:vivifyData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if (error) {
LALog(@"error vivifying: %@",error);
[LACamliUtil errorText:@[@"error vivify: ",error]];
LALog(@"error vivifying: %@", error);
[LACamliUtil errorText:@[
@"error vivify: ",
[error description]
]];
_failedTransfer = YES;
}
@ -197,15 +236,22 @@ static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
- (void)finished
{
[LACamliUtil statusText:@[@"cleaning up..."]];
[LACamliUtil statusText:@[
@"cleaning up..."
]];
_client.backgroundID = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"queuesync" expirationHandler:^{
_client.backgroundID = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"queuesync"
expirationHandler:^{
LALog(@"queue sync task expired");
}];
}];
[[UIApplication sharedApplication] endBackgroundTask:_taskID];
LALog(@"finished op %@",_file.blobRef);
LALog(@"finished op %@", _file.blobRef);
// There's an extra retain on this operation that I cannot find,
// this mitigates the issue so the leak is tiny
_file.allBlobs = nil;
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
@ -217,15 +263,29 @@ static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
[self didChangeValueForKey:@"isFinished"];
}
#pragma mark - nsurlsession delegate
- (void)URLSession:(NSURLSession*)session task:(NSURLSessionTask*)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
if ([_client.delegate respondsToSelector:@selector(uploadProgress:
forOperation:)]) {
float progress = (float)totalBytesSent / (float)totalBytesExpectedToSend;
dispatch_async(dispatch_get_main_queue(), ^{
[_client.delegate uploadProgress:progress forOperation:self];
});
}
}
#pragma mark - multipart bits
- (NSMutableData *)multipartDataForChunks
- (NSMutableData*)multipartDataForChunks
{
NSMutableData *data = [NSMutableData data];
NSMutableData* data = [NSMutableData data];
for (NSData *chunk in [_file blobsToUpload]) {
for (NSData* chunk in [_file blobsToUpload]) {
[data appendData:[[NSString stringWithFormat:@"--%@\r\n", multipartBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
// server ignores this filename and mimetype, so it doesn't matter what it is
// server ignores this filename and mimetype, it doesn't matter what it is
[data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"image.jpg\"\r\n", [LACamliUtil blobRef:chunk]] dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:[@"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:chunk];
@ -237,21 +297,26 @@ static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
return data;
}
- (NSMutableData *)multipartVivifyDataForChunks
- (NSMutableData*)multipartVivifyDataForChunks
{
NSMutableData *data = [NSMutableData data];
NSMutableData* data = [NSMutableData data];
NSMutableDictionary *schemaBlob = [NSMutableDictionary dictionaryWithObjectsAndKeys:@1, @"camliVersion", @"file", @"camliType", [LACamliUtil rfc3339StringFromDate:_file.creation], @"unixMTime", nil];
NSMutableDictionary* schemaBlob = [NSMutableDictionary dictionaryWithObjectsAndKeys:@1, @"camliVersion", @"file", @"camliType", [LACamliUtil rfc3339StringFromDate:_file.creation], @"unixMTime", nil];
NSMutableArray *parts = [NSMutableArray array];
NSMutableArray* parts = [NSMutableArray array];
int i = 0;
for (NSString *blobRef in _file.allBlobRefs) {
[parts addObject:@{@"blobRef":blobRef,@"size":[NSNumber numberWithInteger:[[_file.allBlobs objectAtIndex:i] length]]}];
for (NSString* blobRef in _file.allBlobRefs) {
[parts addObject:@{
@"blobRef" : blobRef, @"size" : [NSNumber numberWithInteger:[[_file.allBlobs objectAtIndex:i] length]]
}];
i++;
}
[schemaBlob setObject:parts forKey:@"parts"];
[schemaBlob setObject:parts
forKey:@"parts"];
NSData *schemaData = [NSJSONSerialization dataWithJSONObject:schemaBlob options:NSJSONWritingPrettyPrinted error:nil];
NSData* schemaData = [NSJSONSerialization dataWithJSONObject:schemaBlob
options:NSJSONWritingPrettyPrinted
error:nil];
[data appendData:[[NSString stringWithFormat:@"--%@\r\n", multipartBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"json\"\r\n", [LACamliUtil blobRef:schemaData]] dataUsingEncoding:NSUTF8StringEncoding]];

View File

@ -10,14 +10,14 @@
@interface LACamliUtil : NSObject
+ (NSString *)base64EncodedStringFromString:(NSString *)string;
+ (NSString *)passwordForUsername:(NSString *)username;
+ (BOOL)savePassword:(NSString *)password forUsername:(NSString *)username;
+ (NSString *)blobRef:(NSData *)data;
+ (NSString *)rfc3339StringFromDate:(NSDate *)date;
+ (NSString*)base64EncodedStringFromString:(NSString*)string;
+ (NSString*)passwordForUsername:(NSString*)username;
+ (BOOL)savePassword:(NSString*)password forUsername:(NSString*)username;
+ (NSString*)blobRef:(NSData*)data;
+ (NSString*)rfc3339StringFromDate:(NSDate*)date;
+ (void)logText:(NSArray *)logs;
+ (void)statusText:(NSArray *)statuses;
+ (void)errorText:(NSArray *)errors;
+ (void)logText:(NSArray*)logs;
+ (void)statusText:(NSArray*)statuses;
+ (void)errorText:(NSArray*)errors;
@end

View File

@ -7,21 +7,24 @@
//
#import "LACamliUtil.h"
#import "LAAppDelegate.h"
#import <CommonCrypto/CommonDigest.h>
#import <SSKeychain.h>
@implementation LACamliUtil
static NSString *const serviceName = @"org.camlistore.credentials";
static NSString* const serviceName = @"org.camlistore.credentials";
// h/t AFNetworking
+ (NSString *)base64EncodedStringFromString:(NSString *)string
+ (NSString*)base64EncodedStringFromString:(NSString*)string
{
NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
NSData* data = [NSData dataWithBytes:[string UTF8String]
length:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
NSUInteger length = [data length];
NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
NSMutableData* mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t *input = (uint8_t *)[data bytes];
uint8_t *output = (uint8_t *)[mutableData mutableBytes];
uint8_t* input = (uint8_t*)[data bytes];
uint8_t* output = (uint8_t*)[mutableData mutableBytes];
for (NSUInteger i = 0; i < length; i += 3) {
NSUInteger value = 0;
@ -37,80 +40,48 @@ static NSString *const serviceName = @"org.camlistore.credentials";
NSUInteger idx = (i / 3) * 4;
output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F];
output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F];
output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '=';
output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '=';
output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '=';
output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '=';
}
return [[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding];
return [[NSString alloc] initWithData:mutableData
encoding:NSASCIIStringEncoding];
}
#pragma mark - keychain stuff
+ (NSString *)passwordForUsername:(NSString *)username
+ (NSString*)passwordForUsername:(NSString*)username
{
if (!username) {
LALog(@"no username");
NSError* error;
NSString* password = [SSKeychain passwordForService:CamliCredentialsKey
account:username
error:&error];
if (!password || error) {
[LACamliUtil errorText:@[
@"error getting password: ",
[error description]
]];
return nil;
}
// construct query
NSDictionary *passwordQuery = @{(__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrAccount: username,
(__bridge NSString *)kSecAttrService: serviceName,
(__bridge NSString *)kSecReturnData: (id)kCFBooleanTrue};
NSData *results = nil;
CFTypeRef resultRef = (__bridge CFTypeRef)results;
// actually request password
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)passwordQuery, &resultRef);
results = (__bridge NSData *)resultRef;
if (status == noErr) {
LALog(@"returning valid password");
return [[NSString alloc] initWithData:results encoding:NSUTF8StringEncoding];
}
return nil;
return password;
}
+ (BOOL)savePassword:(NSString *)password forUsername:(NSString *)username
+ (BOOL)savePassword:(NSString*)password forUsername:(NSString*)username
{
if (!password || !username) {
LALog(@"no password or username");
return NO;
}
NSError* error;
BOOL setPassword = [SSKeychain setPassword:password
forService:CamliCredentialsKey
account:username
error:&error];
OSStatus status = noErr;
if (!setPassword || error) {
[LACamliUtil errorText:@[
@"error setting password: ",
[error description]
]];
if ([self passwordForUsername:username]) {
LALog(@"update");
// update keychain item
NSDictionary *updateQuery = @{(__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrAccount: username,
(__bridge NSString *)kSecAttrService: serviceName
};
NSDictionary *updateAttribute = @{(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding]};
status = SecItemUpdate((__bridge CFDictionaryRef)updateQuery, (__bridge CFDictionaryRef)updateAttribute);
} else {
LALog(@"new");
// add a new keychain item
NSDictionary *addQuery = @{(__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrAccount: username,
(__bridge NSString *)kSecAttrService: serviceName,
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding]
};
status = SecItemAdd((__bridge CFDictionaryRef)addQuery, nil);
}
if (status != noErr) {
return NO;
}
@ -119,7 +90,7 @@ static NSString *const serviceName = @"org.camlistore.credentials";
#pragma mark - hashes
+ (NSString *)blobRef:(NSData *)data
+ (NSString*)blobRef:(NSData*)data
{
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
@ -128,7 +99,7 @@ static NSString *const serviceName = @"org.camlistore.credentials";
NSMutableString* output = [NSMutableString stringWithCapacity:(CC_SHA1_DIGEST_LENGTH * 2) + 5];
[output appendString:@"sha1-"];
for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
[output appendFormat:@"%02x", digest[i]];
}
@ -137,11 +108,11 @@ static NSString *const serviceName = @"org.camlistore.credentials";
#pragma mark - dates
+ (NSString *)rfc3339StringFromDate:(NSDate *)date
+ (NSString*)rfc3339StringFromDate:(NSDate*)date
{
NSDateFormatter *rfc3339DateFormatter = [[NSDateFormatter alloc] init];
NSDateFormatter* rfc3339DateFormatter = [[NSDateFormatter alloc] init];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
NSLocale* enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
@ -152,37 +123,46 @@ static NSString *const serviceName = @"org.camlistore.credentials";
#pragma mark - yucky logging hack
+ (void)logText:(NSArray *)logs
+ (void)logText:(NSArray*)logs
{
NSMutableString *logString = [NSMutableString string];
NSMutableString* logString = [NSMutableString string];
for (NSString *log in logs) {
for (NSString* log in logs) {
[logString appendString:log];
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"logtext" object:@{@"text": logString}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"logtext"
object:@{
@"text" : logString
}];
}
+ (void)statusText:(NSArray *)statuses
+ (void)statusText:(NSArray*)statuses
{
NSMutableString *statusString = [NSMutableString string];
NSMutableString* statusString = [NSMutableString string];
for (NSString *status in statuses) {
for (NSString* status in statuses) {
[statusString appendString:status];
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"statusText" object:@{@"text": statusString}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"statusText"
object:@{
@"text" : statusString
}];
}
+ (void)errorText:(NSArray *)errors
+ (void)errorText:(NSArray*)errors
{
NSMutableString *errorString = [NSMutableString string];
NSMutableString* errorString = [NSMutableString string];
for (NSString *error in errors) {
for (NSString* error in errors) {
[errorString appendString:error];
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"errorText" object:@{@"text": errorString}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"errorText"
object:@{
@"text" : errorString
}];
}
@end

View File

@ -11,11 +11,11 @@
@class ProgressViewController;
@interface LAViewController : UIViewController <UITableViewDataSource,UITableViewDelegate,LACamliStatusDelegate>
@interface LAViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, LACamliStatusDelegate>
@property IBOutlet UITableView *table;
@property NSMutableArray *operations;
@property ProgressViewController *progress;
@property IBOutlet UITableView* table;
@property NSMutableArray* operations;
@property ProgressViewController* progress;
- (void)dismissSettings;

View File

@ -14,6 +14,7 @@
#import "LACamliUploadOperation.h"
#import "UploadStatusCell.h"
#import "UploadTaskCell.h"
#import "LACamliFile.h"
@implementation LAViewController
@ -25,32 +26,26 @@
self.navigationItem.title = @"camlistore";
UIBarButtonItem *settingsItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:@selector(showSettings)];
UIBarButtonItem* settingsItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit
target:self
action:@selector(showSettings)];
[self.navigationItem setRightBarButtonItem:settingsItem];
NSURL *serverURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] stringForKey:CamliServerKey]];
NSString *username = [[NSUserDefaults standardUserDefaults] stringForKey:CamliUsernameKey];
NSString *password = nil;
if (username) {
password = [LACamliUtil passwordForUsername:username];
}
if (!serverURL || !username || !password) {
[self showSettings];
}
[[NSNotificationCenter defaultCenter] addObserverForName:@"statusText" object:nil queue:nil usingBlock:^(NSNotification *note) {
UploadStatusCell *cell = (UploadStatusCell *)[_table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
[[NSNotificationCenter defaultCenter] addObserverForName:@"statusText" object:nil queue:nil usingBlock:^(NSNotification *note)
{
UploadStatusCell* cell = (UploadStatusCell*)[_table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0
inSection:0]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.status.text = note.object[@"text"];
});
}];
[[NSNotificationCenter defaultCenter] addObserverForName:@"errorText" object:nil queue:nil usingBlock:^(NSNotification *note) {
UploadStatusCell *cell = (UploadStatusCell *)[_table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
[[NSNotificationCenter defaultCenter] addObserverForName:@"errorText" object:nil queue:nil usingBlock:^(NSNotification *note)
{
UploadStatusCell* cell = (UploadStatusCell*)[_table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0
inSection:0]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.error.text = note.object[@"text"];
@ -58,73 +53,108 @@
}];
}
- (void)viewDidAppear:(BOOL)animated
{
NSURL* serverURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] stringForKey:CamliServerKey]];
NSString* username = [[NSUserDefaults standardUserDefaults] stringForKey:CamliUsernameKey];
NSString* password = nil;
if (username) {
password = [LACamliUtil passwordForUsername:username];
}
if (!serverURL || !username || !password) {
[self showSettings];
}
}
- (void)showSettings
{
SettingsViewController *settings = [self.storyboard instantiateViewControllerWithIdentifier:@"settings"];
SettingsViewController* settings = [self.storyboard instantiateViewControllerWithIdentifier:@"settings"];
[settings setParent:self];
[self presentViewController:settings animated:YES completion:nil];
[self presentViewController:settings
animated:YES
completion:nil];
}
- (void)dismissSettings
{
[self dismissViewControllerAnimated:YES completion:nil];
[self dismissViewControllerAnimated:YES
completion:nil];
[(LAAppDelegate *)[[UIApplication sharedApplication] delegate] loadCredentials];
[(LAAppDelegate*)[[UIApplication sharedApplication] delegate] loadCredentials];
}
#pragma mark - client delegate methods
- (void)addedUploadOperation:(LACamliUploadOperation *)op
- (void)addedUploadOperation:(LACamliUploadOperation*)op
{
NSIndexPath *path = [NSIndexPath indexPathForRow:[_operations count] inSection:1];
@synchronized(_operations)
{
NSIndexPath* path = [NSIndexPath indexPathForRow:[_operations count]
inSection:1];
@synchronized(_operations){
[_operations addObject:op];
[_table insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationAutomatic];
[_table insertRowsAtIndexPaths:@[
path
]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
- (void)finishedUploadOperation:(LACamliUploadOperation *)op
- (void)finishedUploadOperation:(LACamliUploadOperation*)op
{
NSIndexPath *path = [NSIndexPath indexPathForRow:[_operations indexOfObject:op] inSection:1];
NSIndexPath* path = [NSIndexPath indexPathForRow:[_operations indexOfObject:op]
inSection:1];
@synchronized(_operations){
@synchronized(_operations)
{
[_operations removeObject:op];
[_table deleteRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationAutomatic];
[_table deleteRowsAtIndexPaths:@[
path
]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
- (void)uploadProgress:(float)pct forOperation:(LACamliUploadOperation *)op
- (void)uploadProgress:(float)pct forOperation:(LACamliUploadOperation*)op
{
NSIndexPath *path = [NSIndexPath indexPathForRow:[_operations indexOfObject:op] inSection:1];
UploadTaskCell *cell = (UploadTaskCell *)[_table cellForRowAtIndexPath:path];
NSIndexPath* path = [NSIndexPath indexPathForRow:[_operations indexOfObject:op]
inSection:1];
UploadTaskCell* cell = (UploadTaskCell*)[_table cellForRowAtIndexPath:path];
cell.progress.progress = pct;
}
#pragma mark - table view methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell *cell;
if (indexPath.section == 0) {
cell = [tableView dequeueReusableCellWithIdentifier:@"statusCell" forIndexPath:indexPath];
UploadStatusCell* cell = [tableView dequeueReusableCellWithIdentifier:@"statusCell"
forIndexPath:indexPath];
return cell;
} else {
cell = [tableView dequeueReusableCellWithIdentifier:@"taskCell" forIndexPath:indexPath];
UploadTaskCell* cell = [tableView dequeueReusableCellWithIdentifier:@"taskCell"
forIndexPath:indexPath];
LACamliUploadOperation *op = [_operations objectAtIndex:indexPath.row];
cell.progress.progress = 0.0;
[[(UploadTaskCell *)cell displayText] setText:[NSString stringWithFormat:@"%@",[op name]]];
LACamliUploadOperation* op = [_operations objectAtIndex:indexPath.row];
[cell.displayText setText:[NSString stringWithFormat:@"%@", [op name]]];
[cell.preview setImage:[op.file thumbnail]];
return cell;
}
return cell;
return nil;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
- (NSString*)tableView:(UITableView*)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *title = @"";
NSString* title = @"";
if (section == 0) {
title = @"status";
@ -135,12 +165,12 @@
return title;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView
{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
return 1;

View File

@ -12,11 +12,11 @@
@interface SettingsViewController : UIViewController
@property (weak) LAViewController *parent;
@property IBOutlet UILabel *errors;
@property IBOutlet UITextField *server;
@property IBOutlet UITextField *username;
@property IBOutlet UITextField *password;
@property(weak) LAViewController* parent;
@property IBOutlet UILabel* errors;
@property IBOutlet UITextField* server;
@property IBOutlet UITextField* username;
@property IBOutlet UITextField* password;
- (IBAction)validate;

View File

@ -17,9 +17,10 @@
@implementation SettingsViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
self = [super initWithNibName:nibNameOrNil
bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
@ -30,16 +31,16 @@
{
[super viewDidLoad];
NSString *serverUrl = [[NSUserDefaults standardUserDefaults] stringForKey:CamliServerKey];
NSString* serverUrl = [[NSUserDefaults standardUserDefaults] stringForKey:CamliServerKey];
if (serverUrl) {
self.server.text = serverUrl;
}
NSString *username = [[NSUserDefaults standardUserDefaults] stringForKey:CamliUsernameKey];
NSString* username = [[NSUserDefaults standardUserDefaults] stringForKey:CamliUsernameKey];
if (username) {
self.username.text = username;
NSString *password = [LACamliUtil passwordForUsername:username];
NSString* password = [LACamliUtil passwordForUsername:username];
if (password) {
self.password.text = password;
}
@ -48,7 +49,7 @@
#pragma mark - uitextfield delegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField
- (BOOL)textFieldShouldReturn:(UITextField*)textField
{
LALog(@"text field return %@", textField);
@ -73,7 +74,7 @@
BOOL hasErrors = NO;
NSURL *serverUrl = [NSURL URLWithString:self.server.text];
NSURL* serverUrl = [NSURL URLWithString:self.server.text];
if (!serverUrl || !serverUrl.scheme || !serverUrl.host) {
hasErrors = YES;
@ -98,11 +99,16 @@
- (void)saveValues
{
[LACamliUtil savePassword:self.password.text forUsername:self.username.text];
[[NSUserDefaults standardUserDefaults] setObject:self.username.text forKey:CamliUsernameKey];
[[NSUserDefaults standardUserDefaults] setObject:self.server.text forKey:CamliServerKey];
[[NSUserDefaults standardUserDefaults] setObject:self.username.text
forKey:CamliUsernameKey];
[[NSUserDefaults standardUserDefaults] setObject:self.server.text
forKey:CamliServerKey];
[[NSUserDefaults standardUserDefaults] synchronize];
[LACamliUtil errorText:@[@""]];
[LACamliUtil errorText:@[
@""
]];
[self.parent dismissSettings];
}

View File

@ -10,7 +10,7 @@
@interface UploadStatusCell : UITableViewCell
@property IBOutlet UILabel *status;
@property IBOutlet UILabel *error;
@property IBOutlet UILabel* status;
@property IBOutlet UILabel* error;
@end

View File

@ -10,9 +10,10 @@
@implementation UploadStatusCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
self = [super initWithStyle:style
reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
@ -21,7 +22,8 @@
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
[super setSelected:selected
animated:animated];
// Configure the view for the selected state
}

View File

@ -10,8 +10,8 @@
@interface UploadTaskCell : UITableViewCell
@property IBOutlet UILabel *displayText;
@property IBOutlet UIImageView *preview;
@property IBOutlet UIProgressView *progress;
@property IBOutlet UILabel* displayText;
@property IBOutlet UIImageView* preview;
@property IBOutlet UIProgressView* progress;
@end

View File

@ -10,9 +10,10 @@
@implementation UploadTaskCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
self = [super initWithStyle:style
reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
@ -21,7 +22,8 @@
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
[super setSelected:selected
animated:animated];
// Configure the view for the selected state
}

View File

@ -21,7 +21,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>20140109</string>
<string>20140119</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIBackgroundModes</key>