diff --git a/ArqRestoreCommand.m b/ArqRestoreCommand.m index f2b726d..669409f 100644 --- a/ArqRestoreCommand.m +++ b/ArqRestoreCommand.m @@ -44,10 +44,12 @@ #import "ArqSalt.h" #import "ArqRepo.h" #import "BackupSet.h" +#import "ReflogPrinter.h" + @interface ArqRestoreCommand (internal) - (BOOL)printArqFolders:(NSError **)error; -- (BOOL)restorePath:(NSError **)error; +- (BOOL)processPath:(NSError **)error; - (BOOL)validateS3Keys:(NSError **)error; @end @@ -112,7 +114,7 @@ if (path == nil) { ret = [self printArqFolders:error]; } else { - ret = [self restorePath:error]; + ret = [self processPath:error]; } return ret; } @@ -164,7 +166,7 @@ } return YES; } -- (BOOL)restorePath:(NSError **)error { +- (BOOL)processPath:(NSError **)error { if (![self validateS3Keys:error]) { return NO; } @@ -192,19 +194,13 @@ NSString *computerUUID = [path substringWithRange:computerUUIDRange]; NSString *bucketUUID = [path substringWithRange:bucketUUIDRange]; NSString *bucketName = [[plist stringNodeForKey:@"BucketName"] stringValue]; - - printf("restoring %s from ", [bucketName UTF8String]); NSError *uacError = nil; NSData *uacData = [s3 dataAtPath:[NSString stringWithFormat:@"/%@/%@/computerinfo", s3BucketName, computerUUID] error:&uacError]; UserAndComputer *uac = nil; if (uacData != nil) { uac = [[[UserAndComputer alloc] initWithXMLData:uacData error:&uacError] autorelease]; - printf("%s (%s)", [[uac computerName] UTF8String], [[uac userName] UTF8String]); - } else { - printf("(unknown computer)"); } - printf(" to %s/%s\n", [[[NSFileManager defaultManager] currentDirectoryPath] UTF8String], [bucketName UTF8String]); NSError *saltError = nil; ArqSalt *arqSalt = [[[ArqSalt alloc] initWithAccessKeyID:accessKey secretAccessKey:secretKey s3BucketName:s3BucketName computerUUID:computerUUID] autorelease]; @@ -222,11 +218,27 @@ return NO; } - Restorer *restorer = [[[Restorer alloc] initWithRepo:repo bucketName:bucketName commitSHA1:commitSHA1] autorelease]; - if (![restorer restore:error]) { - return NO; + if ([commitSHA1 isEqualToString:@"reflog"]) { + printf("printing reflog for %s\n", [bucketName UTF8String]); + ReflogPrinter *printer = [[[ReflogPrinter alloc] initWithS3BucketName:s3BucketName computerUUID:computerUUID bucketUUID:bucketUUID s3:s3 repo:repo] autorelease]; + if (![printer printReflog:error]) { + return NO; + } + } else { + printf("restoring %s from ", [bucketName UTF8String]); + if (uac != nil) { + printf("%s (%s)", [[uac computerName] UTF8String], [[uac userName] UTF8String]); + } else { + printf("(unknown computer)"); + } + printf(" to %s/%s\n", [[[NSFileManager defaultManager] currentDirectoryPath] UTF8String], [bucketName UTF8String]); + + Restorer *restorer = [[[Restorer alloc] initWithRepo:repo bucketName:bucketName commitSHA1:commitSHA1] autorelease]; + if (![restorer restore:error]) { + return NO; + } + printf("restored files are in %s\n", [bucketName fileSystemRepresentation]); } - printf("restored files are in %s\n", [bucketName fileSystemRepresentation]); return YES; } - (BOOL)validateS3Keys:(NSError **)error { diff --git a/ReflogEntry.h b/ReflogEntry.h new file mode 100644 index 0000000..993e82f --- /dev/null +++ b/ReflogEntry.h @@ -0,0 +1,20 @@ +// +// ReflogEntry.h +// arq_restore +// +// Created by Stefan Reitshamer on 11/20/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +@class BlobKey; + +@interface ReflogEntry : NSObject { + BlobKey *oldHeadBlobKey; + BlobKey *newHeadBlobKey; +} +- (id)initWithData:(NSData *)theData error:(NSError **)error; + +- (BlobKey *)oldHeadBlobKey; +- (BlobKey *)newHeadBlobKey; +@end diff --git a/ReflogEntry.m b/ReflogEntry.m new file mode 100644 index 0000000..24ab650 --- /dev/null +++ b/ReflogEntry.m @@ -0,0 +1,51 @@ +// +// ReflogEntry.m +// arq_restore +// +// Created by Stefan Reitshamer on 11/20/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "ReflogEntry.h" +#import "BlobKey.h" +#import "DictNode.h" +#import "SetNSError.h" + + +@implementation ReflogEntry +- (id)initWithData:(NSData *)theData error:(NSError **)error { + if (self = [super init]) { + DictNode *dictNode = [DictNode dictNodeWithXMLData:theData error:error]; + if (dictNode == nil) { + [self release]; + return nil; + } + if (![dictNode containsKey:@"oldHeadSHA1"] + || ![dictNode containsKey:@"oldHeadStretchKey"] + || ![dictNode containsKey:@"newHeadSHA1"] + || ![dictNode containsKey:@"newHeadStretchKey"]) { + SETNSERROR(@"ReflogEntryErrorDomain", -1, @"missing values in reflog entry"); + [self release]; + return nil; + } + oldHeadBlobKey = [[BlobKey alloc] initWithSHA1:[[dictNode stringNodeForKey:@"oldHeadSHA1"] stringValue] + stretchEncryptionKey:[[dictNode booleanNodeForKey:@"oldHeadStretchKey"] booleanValue]]; + + newHeadBlobKey = [[BlobKey alloc] initWithSHA1:[[dictNode stringNodeForKey:@"newHeadSHA1"] stringValue] + stretchEncryptionKey:[[dictNode booleanNodeForKey:@"newHeadStretchKey"] booleanValue]]; + } + return self; +} +- (void)dealloc { + [oldHeadBlobKey release]; + [newHeadBlobKey release]; + [super dealloc]; +} + +- (BlobKey *)oldHeadBlobKey { + return oldHeadBlobKey; +} +- (BlobKey *)newHeadBlobKey { + return newHeadBlobKey; +} +@end diff --git a/ReflogPrinter.h b/ReflogPrinter.h new file mode 100644 index 0000000..6c558d3 --- /dev/null +++ b/ReflogPrinter.h @@ -0,0 +1,22 @@ +// +// ReflogPrinter.h +// arq_restore +// +// Created by Stefan Reitshamer on 11/21/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +@class S3Service; +@class ArqRepo; + +@interface ReflogPrinter : NSObject { + NSString *s3BucketName; + NSString *computerUUID; + NSString *bucketUUID; + S3Service *s3; + ArqRepo *repo; +} +- (id)initWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID s3:(S3Service *)theS3 repo:(ArqRepo *)theRepo; +- (BOOL)printReflog:(NSError **)error; +@end diff --git a/ReflogPrinter.m b/ReflogPrinter.m new file mode 100644 index 0000000..3458663 --- /dev/null +++ b/ReflogPrinter.m @@ -0,0 +1,99 @@ +// +// ReflogPrinter.m +// arq_restore +// +// Created by Stefan Reitshamer on 11/21/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "ReflogPrinter.h" +#import "S3Service.h" +#import "ArqRepo.h" +#import "ReflogEntry.h" +#import "Commit.h" +#import "BlobKey.h" + + +@interface ReflogPrinter (internal) +- (BOOL)printEntry:(NSString *)path error:(NSError **)error; +@end + + +@implementation ReflogPrinter +- (id)initWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID s3:(S3Service *)theS3 repo:(ArqRepo *)theRepo { + if (self = [super init]) { + s3BucketName = [theS3BucketName retain]; + computerUUID = [theComputerUUID retain]; + bucketUUID = [theBucketUUID retain]; + s3 = [theS3 retain]; + repo = [theRepo retain]; + } + return self; +} +- (void)dealloc { + [s3BucketName release]; + [computerUUID release]; + [bucketUUID release]; + [s3 release]; + [repo release]; + [super dealloc]; +} + +- (BOOL)printReflog:(NSError **)error { + NSString *prefix = [NSString stringWithFormat:@"/%@/%@/bucketdata/%@/refs/logs/master/", s3BucketName, computerUUID, bucketUUID]; + NSArray *paths = [s3 pathsWithPrefix:prefix error:error]; + if (paths == nil) { + return NO; + } + NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:@"description" ascending:NO] autorelease]; + NSArray *sortedPaths = [paths sortedArrayUsingDescriptors:[NSArray arrayWithObject:descriptor]]; + + BOOL ret = YES; + NSAutoreleasePool *pool = nil; + for (NSString *path in sortedPaths) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + if (![self printEntry:path error:error]) { + ret = NO; + break; + } + } + if (!ret && error != NULL) { + [*error retain]; + } + [pool drain]; + if (!ret && error != NULL) { + [*error autorelease]; + } + return ret; +} +@end + +@implementation ReflogPrinter (internal) +- (BOOL)printEntry:(NSString *)path error:(NSError **)error { + printf("reflog %s\n", [path UTF8String]); + + NSData *data = [s3 dataAtPath:path error:error]; + if (data == nil) { + return NO; + } + NSError *myError = nil; + ReflogEntry *entry = [[[ReflogEntry alloc] initWithData:data error:&myError] autorelease]; + if (entry == nil) { + printf("\terror reading reflog entry: %s\n", [[myError description] UTF8String]); + } else { + Commit *commit = [repo commitForBlobKey:[entry newHeadBlobKey] error:&myError]; + if (commit == nil) { + printf("\tcommit %s: %s\n", [[[entry newHeadBlobKey] description] UTF8String], [[myError localizedDescription] UTF8String]); + } else { + printf("\tblobkey: %s\n", [[[entry newHeadBlobKey] description] UTF8String]); + printf("\tauthor: %s\n", [[commit author] UTF8String]); + printf("\tdate: %s\n", [[[commit creationDate] description] UTF8String]); + printf("\tlocation: %s\n", [[commit location] UTF8String]); + printf("\trestore command: arq_restore /%s/%s/buckets/%s %s\n", [s3BucketName UTF8String], [computerUUID UTF8String], [bucketUUID UTF8String], + [[[entry newHeadBlobKey] sha1] UTF8String]); + } + } + return YES; +} +@end diff --git a/arq_restore.m b/arq_restore.m index aa2fc7a..bb7e8bf 100644 --- a/arq_restore.m +++ b/arq_restore.m @@ -39,6 +39,7 @@ static void printUsage(const char *exeName) { fprintf(stderr, "Usage:\n"); fprintf(stderr, "\t%s [-l log_level]\n", exeName); fprintf(stderr, "\t%s [-l log_level] /s3bucket/computerUUID/folderUUID\n", exeName); + fprintf(stderr, "\t%s [-l log_level] /s3bucket/computerUUID/folderUUID reflog\n", exeName); fprintf(stderr, "\t%s [-l log_level] /s3bucket/computerUUID/folderUUID \n", exeName); } int main (int argc, const char **argv) { diff --git a/arq_restore.xcodeproj/project.pbxproj b/arq_restore.xcodeproj/project.pbxproj index 383ac95..f77ed6e 100644 --- a/arq_restore.xcodeproj/project.pbxproj +++ b/arq_restore.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ F805B8CE1160ECD7007EC01E /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8CD1160ECD7007EC01E /* CoreServices.framework */; }; F81426D714541A6C00D7E50A /* BackupSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F81426D614541A6C00D7E50A /* BackupSet.m */; }; F8373E0F14794D01005AFBE6 /* ReflogEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = F8373E0E14794D01005AFBE6 /* ReflogEntry.m */; }; + F8373E5A147A8DEC005AFBE6 /* ReflogPrinter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8373E59147A8DEC005AFBE6 /* ReflogPrinter.m */; }; F83C1A7411CA7C170001958F /* ArqRestoreCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B54C1160D3E6007EC01E /* ArqRestoreCommand.m */; }; F83C1A7711CA7C170001958F /* ArrayNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7421160DCFE007EC01E /* ArrayNode.m */; }; F83C1A7811CA7C170001958F /* BooleanNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7441160DCFE007EC01E /* BooleanNode.m */; }; @@ -342,6 +343,8 @@ F81426D614541A6C00D7E50A /* BackupSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BackupSet.m; sourceTree = ""; }; F8373E0D14794D01005AFBE6 /* ReflogEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReflogEntry.h; sourceTree = ""; }; F8373E0E14794D01005AFBE6 /* ReflogEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReflogEntry.m; sourceTree = ""; }; + F8373E58147A8DEC005AFBE6 /* ReflogPrinter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReflogPrinter.h; sourceTree = ""; }; + F8373E59147A8DEC005AFBE6 /* ReflogPrinter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReflogPrinter.m; sourceTree = ""; }; F83C1A5F11CA7A6B0001958F /* arq_verify.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = arq_verify.m; sourceTree = ""; }; F83C1A6111CA7BD20001958F /* ArqVerifyCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqVerifyCommand.h; sourceTree = ""; }; F83C1A6211CA7BD20001958F /* ArqVerifyCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqVerifyCommand.m; sourceTree = ""; }; @@ -552,6 +555,8 @@ F89A1F3D13FAC6820071D321 /* UserLibrary_Arq.m */, F81426D514541A6C00D7E50A /* BackupSet.h */, F81426D614541A6C00D7E50A /* BackupSet.m */, + F8373E58147A8DEC005AFBE6 /* ReflogPrinter.h */, + F8373E59147A8DEC005AFBE6 /* ReflogPrinter.m */, F8373E0D14794D01005AFBE6 /* ReflogEntry.h */, F8373E0E14794D01005AFBE6 /* ReflogEntry.m */, ); @@ -986,6 +991,7 @@ F89A205A13FAE3010071D321 /* DataOutputStream.m in Sources */, F81426D714541A6C00D7E50A /* BackupSet.m in Sources */, F8373E0F14794D01005AFBE6 /* ReflogEntry.m in Sources */, + F8373E5A147A8DEC005AFBE6 /* ReflogPrinter.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };