diff --git a/FTPKit Tests/FTPKit_Tests.m b/FTPKit Tests/FTPKit_Tests.m
index 661e416..88de5ab 100644
--- a/FTPKit Tests/FTPKit_Tests.m
+++ b/FTPKit Tests/FTPKit_Tests.m
@@ -42,6 +42,10 @@ - (void)testFtp
{
FTPClient * ftp = [[FTPClient alloc] initWithHost:@"localhost" port:21 username:@"unittest" password:@"unitpass"];
+ // Sanity. Make sure the root path exists. This should always be true.
+ BOOL success = [ftp directoryExistsAtPath:@"/"];
+ XCTAssertTrue(success, @"");
+
NSArray *contents = [ftp listContentsAtPath:@"/test" showHiddenFiles:YES];
//XCTAssertNil(contents, @"Directory should not exist");
XCTAssertEqual(0, contents.count, @"");
@@ -56,7 +60,7 @@ - (void)testFtp
NSURL *localUrl = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:@"ftplib.tgz"];
// Download 'ftplib.tgz'
- BOOL success = [ftp downloadFile:@"/ftplib.tgz" to:localUrl.path progress:NULL];
+ success = [ftp downloadFile:@"/ftplib.tgz" to:localUrl.path progress:NULL];
XCTAssertTrue(success, @"");
// Upload 'ftplib.tgz' as 'copy.tgz'
@@ -80,7 +84,7 @@ - (void)testFtp
XCTAssertTrue(exists, @"");
exists = [ftp directoryExistsAtPath:@"/badpath"];
- XCTAssertTrue(exists, @"");
+ XCTAssertFalse(exists, @"");
bytes = [ftp fileSizeAtPath:@"/badpath.txt"];
XCTAssertEqual(-1, bytes, @"");
@@ -107,6 +111,25 @@ - (void)testFtp
success = [ftp createDirectoryAtPath:@"/test/test2"];
XCTAssertTrue(success, @"");
+ NSString *cwd = [ftp printWorkingDirectory];
+ XCTAssertTrue([cwd isEqualToString:@"/"], @"");
+
+ // Change directory to /test
+ success = [ftp changeDirectoryToPath:@"/test"];
+ XCTAssertTrue(success, @"");
+
+ /**
+ Currently the connection is not left open between calls and therefore we
+ will always be put back to the root directory when each command is sent.
+
+ Uncomment this when the same connection is used between commands.
+
+ // Make sure we are still in /test.
+ cwd = [ftp printWorkingDirectory];
+ NSLog(@"cwd is %@", cwd);
+ XCTAssertTrue([cwd isEqualToString:@"/test"], @"");
+ */
+
// List contents of 'test'
contents = [ftp listContentsAtPath:@"/test" showHiddenFiles:YES];
diff --git a/FTPKit.xcodeproj/project.xcworkspace/xcshareddata/FTPKit.xccheckout b/FTPKit.xcodeproj/project.xcworkspace/xcshareddata/FTPKit.xccheckout
index 7204b8a..f9cdbd2 100644
--- a/FTPKit.xcodeproj/project.xcworkspace/xcshareddata/FTPKit.xccheckout
+++ b/FTPKit.xcodeproj/project.xcworkspace/xcshareddata/FTPKit.xccheckout
@@ -5,7 +5,7 @@
IDESourceControlProjectFavoriteDictionaryKey
IDESourceControlProjectIdentifier
- 540164E0-A7B5-4DAB-B0C4-5F108479B2B0
+ 318C0C2B-1164-4EF8-AF86-3CEB547A24B6
IDESourceControlProjectName
FTPKit
IDESourceControlProjectOriginsDictionary
diff --git a/FTPKit.xcodeproj/project.xcworkspace/xcuserdata/eric.xcuserdatad/UserInterfaceState.xcuserstate b/FTPKit.xcodeproj/project.xcworkspace/xcuserdata/eric.xcuserdatad/UserInterfaceState.xcuserstate
index 396038c..0a180f5 100644
Binary files a/FTPKit.xcodeproj/project.xcworkspace/xcuserdata/eric.xcuserdatad/UserInterfaceState.xcuserstate and b/FTPKit.xcodeproj/project.xcworkspace/xcuserdata/eric.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/FTPKit/FTPClient.h b/FTPKit/FTPClient.h
index dfa44c7..a99779a 100644
--- a/FTPKit/FTPClient.h
+++ b/FTPKit/FTPClient.h
@@ -4,6 +4,10 @@
Consider implementing more of the commands specified at:
http://en.wikipedia.org/wiki/List_of_FTP_commands
+ Currently this creates a new connection to the FTP server for every command
+ issued. This means the state of the current working directory is NOT kept and.
+ therefore, some commands are not of use.
+
*/
#import "FTPHandle.h"
@@ -414,12 +418,70 @@
*/
- (NSDate *)lastModifiedAtPath:(NSString *)remotePath;
+/**
+ Refer to lastModifiedAtPath:
+
+ This adds the ability to perform the operation asynchronously.
+
+ @param remotePath Remote path to check
+ @param success Method called when process succeeds. 'lastModified' is the
+ last modified time.
+ @param failure Method called when process fails.
+ */
+- (void)lastModifiedAtPath:(NSString *)remotePath
+ success:(void (^)(NSDate *lastModified))success
+ failure:(void (^)(NSError *error))failure;
+
/**
Check if a remote directory exists.
+ Please note that this internally calls [self changeDirectoryToPath:] and does
+ _not_ change back to the previous directory!
+
@param remotePath Directory to check
@return YES if the directory exists. NO, otherwise
*/
- (BOOL)directoryExistsAtPath:(NSString *)remotePath;
+/**
+ Refer to directoryExistsAtPath:
+
+ This adds the ability to perform the operation asynchronously.
+
+ @param remotePath Remote path to check
+ @param success Method called when process succeeds. 'exists' will be YES if the
+ directory exists. NO otherwise.
+ @param failure Method called when process fails.
+ */
+- (void)directoryExistsAtPath:(NSString *)remotePath
+ success:(void (^)(BOOL exists))success
+ failure:(void (^)(NSError *error))failure;
+
+/**
+ Change the working directory to remotePath.
+
+ @note This is currently used ONLY to determine if a directory exists on the
+ server. The state of the cwd is not saved between commands being issued. This
+ is because a new connection is created for every command issued.
+
+ Therefore, in its current state, it is used in a very limited scope. Eventually
+ you will be able to issue commands in the cwd. Not right now.
+
+ @param remotePath Remote directory path to make current directory.
+ @return YES if the directory was successfully changed.
+ */
+- (BOOL)changeDirectoryToPath:(NSString *)remotePath;
+
+/**
+ Returns the current working directory.
+
+ @note Currently this will always return the root path. This is because the
+ lib creates a new connection for every command issued to the server -- and
+ therefore the command will always being in the root path when issuing the
+ command.
+
+ @return The current working directory.
+ */
+- (NSString *)printWorkingDirectory;
+
@end
\ No newline at end of file
diff --git a/FTPKit/FTPClient.m b/FTPKit/FTPClient.m
index a8a53d7..86dbe54 100644
--- a/FTPKit/FTPClient.m
+++ b/FTPKit/FTPClient.m
@@ -57,6 +57,14 @@ - (NSArray *)parseListData:(NSData *)data handle:(FTPHandle *)handle showHiddent
*/
- (void)failedWithMessage:(NSString *)message;
+/**
+ Convenience method that wraps failure(error) in dispatch_async(main_queue)
+ and ensures that the error is copied before sending back to callee -- to ensure
+ it doesn't get nil'ed out by the next command before the callee has a chance
+ to read the error.
+ */
+- (void)returnFailure:(void (^)(NSError *error))failure;
+
@end
@implementation FTPClient
@@ -97,8 +105,7 @@ - (long long int)fileSizeAtPath:(NSString *)path
int stat = FtpSize(cPath, &bytes, FTPLIB_BINARY, conn);
FtpQuit(conn);
if (stat == 0) {
- FKLogError(@"SIZE %@", path);
- self.lastError = [NSError FTPKitErrorWithCode:451];
+ FKLogError(@"File most likely does not exist %@", path);
return -1;
}
FKLogDebug(@"%@ bytes %d", path, bytes);
@@ -139,6 +146,11 @@ - (NSArray *)listContentsAtHandle:(FTPHandle *)handle showHiddenFiles:(BOOL)show
self.lastError = error;
return nil;
}
+ /**
+ Please note: If there are no contents in the folder OR if the folder does
+ not exist data.bytes _will_ be 0. Therefore, you can not use this method to
+ determine if a directory exists!
+ */
[[NSFileManager defaultManager] removeItemAtPath:tmpPath error:&error];
// Log the error, but do not fail.
if (error) {
@@ -157,9 +169,7 @@ - (void)listContentsAtHandle:(FTPHandle *)handle showHiddenFiles:(BOOL)showHidde
success(contents);
});
} else if (! contents && failure) {
- dispatch_async(dispatch_get_main_queue(), ^{
- failure(_lastError);
- });
+ [self returnFailure:failure];
}
});
}
@@ -202,9 +212,7 @@ - (void)downloadHandle:(FTPHandle *)handle to:(NSString *)localPath progress:(BO
success();
});
} else if (! ret && failure) {
- dispatch_async(dispatch_get_main_queue(), ^{
- failure(_lastError);
- });
+ [self returnFailure:failure];
}
});
}
@@ -239,9 +247,7 @@ - (void)uploadFile:(NSString *)localPath to:(NSString *)remotePath progress:(BOO
success();
});
} else if (! ret && failure) {
- dispatch_async(dispatch_get_main_queue(), ^{
- failure(_lastError);
- });
+ [self returnFailure:failure];
}
});
}
@@ -281,9 +287,7 @@ - (void)createDirectoryAtHandle:(FTPHandle *)handle success:(void (^)(void))succ
success();
});
} else if (! ret && failure) {
- dispatch_async(dispatch_get_main_queue(), ^{
- failure(_lastError);
- });
+ [self returnFailure:failure];
}
});
}
@@ -337,9 +341,7 @@ - (void)deleteHandle:(FTPHandle *)handle success:(void (^)(void))success failure
success();
});
} else if (! ret && failure) {
- dispatch_async(dispatch_get_main_queue(), ^{
- failure(_lastError);
- });
+ [self returnFailure:failure];
}
});
}
@@ -385,9 +387,7 @@ - (void)chmodHandle:(FTPHandle *)handle toMode:(int)mode success:(void (^)(void)
success();
});
} else if (! ret && failure) {
- dispatch_async(dispatch_get_main_queue(), ^{
- failure(_lastError);
- });
+ [self returnFailure:failure];
}
});
}
@@ -418,9 +418,7 @@ - (void)renamePath:(NSString *)sourcePath to:(NSString *)destPath success:(void
success();
});
} else if (! ret && failure) {
- dispatch_async(dispatch_get_main_queue(), ^{
- failure(_lastError);
- });
+ [self returnFailure:failure];
}
});
}
@@ -453,9 +451,7 @@ - (void)copyPath:(NSString *)sourcePath to:(NSString *)destPath success:(void (^
success();
});
} else if (! ret && failure) {
- dispatch_async(dispatch_get_main_queue(), ^{
- failure(_lastError);
- });
+ [self returnFailure:failure];
}
});
}
@@ -600,12 +596,108 @@ - (NSDate *)lastModifiedAtPath:(NSString *)remotePath
return date;
}
+- (void)lastModifiedAtPath:(NSString *)remotePath success:(void (^)(NSDate *))success failure:(void (^)(NSError *))failure
+{
+ dispatch_async(_queue, ^{
+ NSDate *date = [self lastModifiedAtPath:remotePath];
+ if (! _lastError && success) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ success(date);
+ });
+ } else if (_lastError && failure) {
+ [self returnFailure:failure];
+ }
+ });
+}
+
- (BOOL)directoryExistsAtPath:(NSString *)remotePath
{
- NSArray *contents = [self listContentsAtPath:remotePath showHiddenFiles:NO];
- if (contents)
+ /**
+ Test the directory by changing to the directory. If the process succeeds
+ then the directory exists.
+
+ The process is to get the current working directory and change _back_ to
+ the previous current working directory. There is a possibility that the
+ second changeDirectoryToPath: may fail! This is really the price we pay
+ for this command as there is no other accurate way to determine this.
+
+ Using listContentsAtPath:showHiddenFiles: will fail as it will return empty
+ contents even if the directory doesn't exist! So long as the command
+ _succeeds_ it will return an empty list.
+
+ // Get the current working directory. We will change back to this directory
+ // if necessary.
+ NSString *cwd = [self printWorkingDirectory];
+ // No need to continue. We already know the path exists by the fact that we
+ // are currently _in_ the directory.
+ if ([cwd isEqualToString:remotePath])
return YES;
- return NO;
+ // Test directory by changing to it.
+ BOOL success = [self changeDirectoryToPath:remotePath];
+ // Attempt to change back to the previous directory.
+ if (success)
+ [self changeDirectoryToPath:cwd];
+ return success;
+ */
+
+ /**
+ Currently the lib creates a new connection for every command issued.
+ Therefore, it is unnecessary to change back to the original cwd.
+ */
+ BOOL success = [self changeDirectoryToPath:remotePath];
+ return success;
+}
+
+- (void)directoryExistsAtPath:(NSString *)remotePath success:(void (^)(BOOL))success failure:(void (^)(NSError *))failure
+{
+ dispatch_async(_queue, ^{
+ BOOL exists = [self directoryExistsAtPath:remotePath];
+ if (! _lastError && success) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ success(exists);
+ });
+ } else if (_lastError && failure) {
+ [self returnFailure:failure];
+ }
+ });
+}
+
+- (BOOL)changeDirectoryToPath:(NSString *)remotePath
+{
+ netbuf *conn = [self connect];
+ if (conn == NULL)
+ return NO;
+ const char *cPath = [remotePath cStringUsingEncoding:NSUTF8StringEncoding];
+ int stat = FtpChdir(cPath, conn);
+ FtpQuit(conn);
+ if (stat == 0) {
+ self.lastError = [NSError FTPKitErrorWithCode:450];
+ return NO;
+ }
+ return YES;
+}
+
+- (NSString *)printWorkingDirectory
+{
+ netbuf *conn = [self connect];
+ if (conn == NULL)
+ return NO;
+ char cPath[kFTPKitTempBufferSize];
+ int stat = FtpPwd(cPath, kFTPKitTempBufferSize, conn);
+ FtpQuit(conn);
+ if (stat == 0) {
+ self.lastError = [NSError FTPKitErrorWithCode:450];
+ return NO;
+ }
+ return [NSString stringWithCString:cPath encoding:NSUTF8StringEncoding];
+}
+
+- (void)returnFailure:(void (^)(NSError *))failure
+{
+ NSError *error = [_lastError copy];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ failure(error);
+ });
}
@end
diff --git a/Libraries/include/ftplib/src/ftplib.c b/Libraries/include/ftplib/src/ftplib.c
index d651e44..c2318ff 100644
--- a/Libraries/include/ftplib/src/ftplib.c
+++ b/Libraries/include/ftplib/src/ftplib.c
@@ -1359,7 +1359,7 @@ GLOBALDEF int FtpPwd(char *path, int max, netbuf *nControl)
int l = max;
char *b = path;
char *s;
- if (!FtpSendCmd("PWD",'2',nControl))
+ if (!FtpSendCmd("PWD", '2', nControl))
return 0;
s = strchr(nControl->response, '"');
if (s == NULL)
diff --git a/README.md b/README.md
index a645a29..e053499 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# FTPKit
-Version 1.0.0b RC2
+Version 1.2.0
FTPKit is an Objective-C library providing facilities implementing the client
side of the File Transfer Protocol (FTP).
@@ -164,6 +164,26 @@ Please note that the `progress:` parameter has not yet been implemented.
// Display an error...
}];
+## Check if a directory exists
+
+ BOOL success = [ftp directoryExistsAtPath:@"/mypath"];
+ if (! success) {
+ // Display an error...
+ }
+
+ ...
+
+ // Or, make the call asynchronous;
+ [ftp directoryExistsAtPath:@"/mypath" success:^(BOOL exists) {
+ if (exists) {
+ // The file exists.
+ } else {
+ // The file doesn't exist.
+ }
+ } failure:^(NSError *error) {
+ // Display an error...
+ }];
+
# Setup & Integration
## Requirements
diff --git a/Sample/FTPKitSample/FTPKitSample.xcodeproj/project.xcworkspace/xcuserdata/eric.xcuserdatad/UserInterfaceState.xcuserstate b/Sample/FTPKitSample/FTPKitSample.xcodeproj/project.xcworkspace/xcuserdata/eric.xcuserdatad/UserInterfaceState.xcuserstate
index ae0b7d1..1dcf190 100644
Binary files a/Sample/FTPKitSample/FTPKitSample.xcodeproj/project.xcworkspace/xcuserdata/eric.xcuserdatad/UserInterfaceState.xcuserstate and b/Sample/FTPKitSample/FTPKitSample.xcodeproj/project.xcworkspace/xcuserdata/eric.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/Sample/FTPKitSample/FTPKitSample/Tests/GeneralTest.m b/Sample/FTPKitSample/FTPKitSample/Tests/GeneralTest.m
index 168e1a8..6906987 100644
--- a/Sample/FTPKitSample/FTPKitSample/Tests/GeneralTest.m
+++ b/Sample/FTPKitSample/FTPKitSample/Tests/GeneralTest.m
@@ -12,6 +12,16 @@ - (void)run
self.ftp = [FTPClient clientWithHost:@"localhost" port:21 username:@"unittest" password:@"unitpass"];
NSURL *localUrl = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:@"ftplib.tgz"];
+ [ftp directoryExistsAtPath:@"/" success:^(BOOL exists) {
+ if (exists) {
+ NSLog(@"Success: 000");
+ } else {
+ NSLog(@"Error: Root path '/' must exist");
+ }
+ } failure:^(NSError *error) {
+ NSLog(@"Error: %@", error.localizedDescription);
+ }];
+
// Note: All of these actions will queue in the order they are called.
// Note: All of these tests are 1 to 1 relationship with the tests used within
// the FTPKit, except the actions are synchronized.
@@ -101,7 +111,7 @@ - (void)run
[ftp deleteDirectoryAtPath:@"/test" success:^(void) {
NSLog(@"Error: Should have failed!");
} failure:^(NSError *error) {
- NSLog(@"Success 011. Error: %@", error.localizedDescription);
+ NSLog(@"Success 011 -- Error: %@", error.localizedDescription);
}];
[ftp deleteFileAtPath:@"/test/copy.tgz" success:^(void) {
@@ -121,6 +131,22 @@ - (void)run
} failure:^(NSError *error) {
NSLog(@"Error: %@", error.localizedDescription);
}];
+
+ [ftp directoryExistsAtPath:@"/badpath" success:^(BOOL exists) {
+ if (exists) {
+ NSLog(@"Error: /badpath should not exist");
+ } else {
+ NSLog(@"Success 015");
+ }
+ } failure:^(NSError *error) {
+ NSLog(@"Error: %@", error.localizedDescription);
+ }];
+
+ [ftp lastModifiedAtPath:@"/ftplib.tgz" success:^(NSDate *lastModified) {
+ NSLog(@"Success 016 -- Date: %@", lastModified);
+ } failure:^(NSError *error) {
+ NSLog(@"Error: %@", error.localizedDescription);
+ }];
}
@end