mirror of https://github.com/perkeep/perkeep.git
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:
parent
7c805515bc
commit
12e449564d
|
@ -1,3 +1,4 @@
|
|||
platform :ios, '7.0'
|
||||
|
||||
pod 'HockeySDK', '~> 3.5.0'
|
||||
pod 'HockeySDK', '~> 3.5.0'
|
||||
pod 'SSKeychain', '~> 1.2.1'
|
|
@ -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
|
||||
|
|
|
@ -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).
|
|
@ -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;
|
||||
|
|
|
@ -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:.
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
@interface UploadStatusCell : UITableViewCell
|
||||
|
||||
@property IBOutlet UILabel *status;
|
||||
@property IBOutlet UILabel *error;
|
||||
@property IBOutlet UILabel* status;
|
||||
@property IBOutlet UILabel* error;
|
||||
|
||||
@end
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue