Git Product home page Git Product logo

dropbox-sdk-obj-c's Introduction

Dropbox for Objective-C

The Official Dropbox Objective-C SDK for integrating with Dropbox API v2 on iOS or macOS.

Full documentation here.

NOTE: Please do not rely on master in production. Please instead use one of our tagged release commits (preferrably fetched via CocoaPods or Carthage), as these commits have been more thoroughly tested.


Table of Contents


System requirements

  • iOS 11.0+
  • macOS 10.10+
  • Xcode 8+ (11.0+ if you use Carthage)

Xcode 8 and iOS 10 bugs

Keychain bug

The Dropbox Objective-C SDK currently supports Xcode 8 and iOS 10. However, there appears to be a bug with the Keychain in the iOS simulator environment where data is not persistently saved to the Keychain.

As a temporary workaround, in the Project Navigator, select your project > Capabilities > Keychain Sharing > ON.

You can read more about the bug here.

Longpoll session timeout bug

Currently, there is a bug with iOS 10 where our longpoll requests timeout after ~6 minutes (instead of our max supported timeframe of 8 minutes (480 seconds)).

For this reason, we recommend that all longpoll calls be made using -listFolderLongpoll:timeout:, with a specified timeout values of <= 300 seconds (5 minutes), until this issue is resolved by Apple.

Read more about the issue here.

Get started

Register your application

Before using this SDK, you should register your application in the Dropbox App Console. This creates a record of your app with Dropbox that will be associated with the API calls you make.

Obtain an OAuth 2.0 token

All requests need to be made with an OAuth 2.0 access token. An OAuth token represents an authenticated link between a Dropbox app and a Dropbox user account or team.

Once you've created an app, you can go to the App Console and manually generate an access token to authorize your app to access your own Dropbox account. Otherwise, you can obtain an OAuth token programmatically using the SDK's pre-defined auth flow. For more information, see below.


SDK distribution

You can integrate the Dropbox Objective-C SDK into your project using one of several methods.

CocoaPods

To use CocoaPods, a dependency manager for Cocoa projects, you should first install it using the following command:

$ gem install cocoapods

Then navigate to the directory that contains your project and create a new file called Podfile. You can do this either with pod init, or open an existing Podfile, and then add pod 'ObjectiveDropboxOfficial' to the main loop. Your Podfile should look something like this:

iOS
platform :ios, '9.0'
use_frameworks!

target '<YOUR_PROJECT_NAME>' do
    pod 'ObjectiveDropboxOfficial'
end
macOS
platform :osx, '10.10'
use_frameworks!

target '<YOUR_PROJECT_NAME>' do
    pod 'ObjectiveDropboxOfficial'
end

Then, after ensuring that your project window in Xcode is closed, run the following command to install the dependency:

$ pod install

Once this command completes, open the newly create .xcworkspace file. Your project should now be successfully integrated with the the SDK.

From here, you can pull SDK updates using the following command:

$ pod update
Common issues
Undefined architecture

If Xcode errors with a message about Undefined symbols for architecture..., try the following:

  • Project Navigator > build target > Build Settings > Other Linker Flags add $(inherited) and -ObjC.

Carthage

You can also integrate the Dropbox Objective-C SDK into your project using Carthage, a decentralized dependency manager for Cocoa. Carthage offers more flexibility than CocoaPods, but requires some additional work. Carthage 0.37.0 is required due to XCFramework requirements on Xcode 12. You can install Carthage (with Xcode 11+) via Homebrew:

brew update
brew install carthage

To install the Dropbox Objective-C SDK via Carthage, you need to create a Cartfile in your project with the following contents:

# ObjectiveDropboxOfficial
github "https://github.com/dropbox/dropbox-sdk-obj-c" ~> 7.3.1

To integrate the Dropbox Objective-C SDK into your project, take the following steps:

Run the following command to checkout and build the Dropbox Objective-C SDK repository:

iOS
carthage update --platform iOS --use-xcframeworks
macOS
carthage update --platform Mac --use-xcframeworks

Then, in the Project Navigator in Xcode, select your project, and then navigate to your project's build target > General > Frameworks, Libraries and Embedded Content. Drag the ObjectiveDropboxOfficial.xcframework file from Carthage/Build into the table and choose Embed & Sign.


Manually add subproject

Finally, you can also integrate the Dropbox Objective-C SDK into your project manually with the help of Carthage. Please take the following steps:

Create a Cartfile in your project with the same contents as the Cartfile listed in the Carthage section of the README.

Then, run the following command to checkout and build the Dropbox Objective-C SDK repository:

iOS
carthage update --platform iOS --use-xcframeworks
macOS
carthage update --platform Mac --use-xcframeworks

Once you have checked-out out all the necessary code via Carthage, drag the Carthage/Checkouts/ObjectiveDropboxOfficial/Source/ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.xcodeproj file into your project as a subproject.


Configure your project

Once you have integrated the Dropbox Objective-C SDK into your project, there are a few additional steps to take before you can begin making API calls.

Application .plist file

You will need to modify your application's .plist to handle Apple's new security changes to the canOpenURL function. You should add the following code to your application's .plist file:

<key>LSApplicationQueriesSchemes</key>
    <array>
        <string>dbapi-8-emm</string>
        <string>dbapi-2</string>
    </array>

This allows the Objective-C SDK to determine if the official Dropbox iOS app is installed on the current device. If it is installed, then the official Dropbox iOS app can be used to programmatically obtain an OAuth 2.0 access token.

Additionally, your application needs to register to handle a unique Dropbox URL scheme for redirect following completion of the OAuth 2.0 authorization flow. This URL scheme should have the format db-<APP_KEY>, where <APP_KEY> is your Dropbox app's app key, which can be found in the App Console.

You should add the following code to your .plist file (but be sure to replace <APP_KEY> with your app's app key):

<key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>db-<APP_KEY></string>
            </array>
            <key>CFBundleURLName</key>
            <string></string>
        </dict>
    </array>

After you've made the above changes, your application's .plist file should look something like this:

Info .plist Example


Handling the authorization flow

There are three methods to programmatically retrieve an OAuth 2.0 access token:

  • Direct auth (iOS only): This launches the official Dropbox iOS app (if installed), authenticates via the official app, then redirects back into the SDK
  • Safari view controller auth (iOS only): This launches a SFSafariViewController to facillitate the auth flow. This is desirable because it is safer for the end-user, and pre-existing session data can be used to avoid requiring the user to re-enter their Dropbox credentials.
  • Redirect to external browser (macOS only): This launches the user's default browser to facillitate the auth flow. This is also desirable because it is safer for the end-user, and pre-existing session data can be used to avoid requiring the user to re-enter their Dropbox credentials.

To facilitate the above authorization flows, you should take the following steps:


Initialize a DBUserClient instance

iOS
#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [DBClientsManager setupWithAppKey:@"<APP_KEY>"];
  return YES;
}
macOS
#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  [DBClientsManager setupWithAppKeyDesktop:@"<APP_KEY>"];
}

Begin the authorization flow

You can commence the auth flow by calling authorizeFromControllerV2:controller:openURL method in your application's view controller.

Please ensure that the supplied view controller is the top-most controller, so that the authorization view displays correctly.

iOS
#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

- (void)myButtonInControllerPressed {
  // OAuth 2 code flow with PKCE that grants a short-lived token with scopes, and performs refreshes of the token automatically.
  DBScopeRequest *scopeRequest = [[DBScopeRequest alloc] initWithScopeType:DBScopeTypeUser
                                                                    scopes:@[@"account_info.read"]
                                                      includeGrantedScopes:NO];
  [DBClientsManager authorizeFromControllerV2:[UIApplication sharedApplication]
                                   controller:[[self class] topMostController]
                        loadingStatusDelegate:nil
                                      openURL:^(NSURL *url) { [[UIApplication sharedApplication] openURL:url]; }
                                 scopeRequest:scopeRequest];
}

+ (UIViewController*)topMostController
{
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    return topController;
}
macOS
#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

- (void)myButtonInControllerPressed {
  // OAuth 2 code flow with PKCE that grants a short-lived token with scopes, and performs refreshes of the token automatically.
  DBScopeRequest *scopeRequest = [[DBScopeRequest alloc] initWithScopeType:DBScopeTypeUser
                                                                    scopes:@[@"account_info.read"]
                                                      includeGrantedScopes:NO];
  [DBClientsManager authorizeFromControllerDesktopV2:[NSWorkspace sharedWorkspace]
                                          controller:self
                               loadingStatusDelegate:nil
                                             openURL:^(NSURL *url) { [[NSWorkspace sharedWorkspace] openURL:url]; }
                                        scopeRequest:scopeRequest];
}

Beginning the authentication flow on mobile will launch a window like this:

Auth Flow Init Example


Handle redirect back into SDK

To handle the redirection back into the Objective-C SDK once the authentication flow is complete, you should add the following code in your application's delegate:

iOS
#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
  DBOAuthCompletion completion = ^(DBOAuthResult *authResult) {
    if (authResult != nil) {
      if ([authResult isSuccess]) {
        NSLog(@"\n\nSuccess! User is logged into Dropbox.\n\n");
      } else if ([authResult isCancel]) {
        NSLog(@"\n\nAuthorization flow was manually canceled by user!\n\n");
      } else if ([authResult isError]) {
        NSLog(@"\n\nError: %@\n\n", authResult);
      }
    }
  };
  BOOL canHandle = [DBClientsManager handleRedirectURL:url completion:completion];
  return canHandle;
}

Or if your app is iOS13+, or your app also supports Scenes, add the following code into your application's main scene delegate:

#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

- (void)scene:(UIScene *)scene
        openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
  DBOAuthCompletion completion = ^(DBOAuthResult *authResult) {
    if (authResult != nil) {
      if ([authResult isSuccess]) {
        NSLog(@"\n\nSuccess! User is logged into Dropbox.\n\n");
      } else if ([authResult isCancel]) {
        NSLog(@"\n\nAuthorization flow was manually canceled by user!\n\n");
      } else if ([authResult isError]) {
        NSLog(@"\n\nError: %@\n\n", authResult);
      }
    }
  };
  for (UIOpenURLContext *context in URLContexts) {
    if ([DBClientsManager handleRedirectURL:context.URL completion:completion]) {
      // stop iterating after the first handle-able url
      break;
    }
  }
}
macOS
#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

// generic launch handler
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
  [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
                                                     andSelector:@selector(handleAppleEvent:withReplyEvent:)
                                                   forEventClass:kInternetEventClass
                                                      andEventID:kAEGetURL];
}

// custom handler
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
  NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
  DBOAuthCompletion oauthCompletion = ^(DBOAuthResult *authResult) {
    if (authResult != nil) {
      if ([authResult isSuccess]) {
        NSLog(@"\n\nSuccess! User is logged into Dropbox.\n\n");
      } else if ([authResult isCancel]) {
        NSLog(@"\n\nAuthorization flow was manually canceled by user!\n\n");
      } else if ([authResult isError]) {
        NSLog(@"\n\nError: %@\n\n", authResult);
      }
      // this forces your app to the foreground, after it has handled the browser redirect
      [[NSRunningApplication currentApplication]
          activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
    }
  };
  [DBClientsManager handleRedirectURL:url completion:oauthCompletion];
}

After the end user signs in with their Dropbox login credentials on mobile, they will see a window like this:

Auth Flow Approval Example

If they press Allow or Cancel, the db-<APP_KEY> redirect URL will be launched from the view controller, and will be handled in your application delegate's application:openURL:options: method, from which the result of the authorization can be parsed.

Now you're ready to begin making API requests!


Try some API requests

Once you have obtained an OAuth 2.0 token, you can try some API v2 calls using the Objective-C SDK.

Dropbox client instance

Start by creating a reference to the DBUserClient or DBTeamClient instance that you will use to make your API calls.

#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

// Reference after programmatic auth flow
DBUserClient *client = [DBClientsManager authorizedClient];

or

#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

// Initialize with manually retrieved auth token
DBUserClient *client = [[DBUserClient alloc] initWithAccessToken:@"<MY_ACCESS_TOKEN>"];

Handle the API response

The Dropbox User API and Business API have three types of requests: RPC, Upload and Download.

The response handlers for each request type are similar to one another. The arguments for the handler blocks are as follows:

  • route result type (DBNilObject if the route does not have a return type)
  • route-specific error (usually a union type)
  • network request error (generic to all requests -- contains information like request ID, HTTP status code, etc.)
  • output content (NSURL / NSData reference to downloaded output for Download-style endpoints only)

Response handlers are required for all endpoints. Progress handlers, on the other hand, are optional for all endpoints.

Note: The Objective-C SDK uses NSNumber objects in place of boolean values. This is done so that nullability can be represented in some of our API response values. For this reason, you should be careful when writing checks like if (myAPIObject.isSomething), which is checking nullability rather than value. Instead, you should use if ([myAPIObject.isSomething boolValue]), which converts the NSNumber field to a boolean value before using it in the if check.


Request types

RPC-style request

[[client.filesRoutes createFolder:@"/test/path/in/Dropbox/account"]
    setResponseBlock:^(DBFILESFolderMetadata *result, DBFILESCreateFolderError *routeError, DBRequestError *networkError) {
      if (result) {
        NSLog(@"%@\n", result);
      } else {
        NSLog(@"%@\n%@\n", routeError, networkError);
      }
    }];

-createFolder:

Here's an example for listing a folder's contents. In the response handler, we repeatedly call listFolderContinue: (for large folders) until we've listed the entire folder:

[[client.filesRoutes listFolder:@"/test/path/in/Dropbox/account"]
    setResponseBlock:^(DBFILESListFolderResult *response, DBFILESListFolderError *routeError, DBRequestError *networkError) {
      if (response) {
        NSArray<DBFILESMetadata *> *entries = response.entries;
        NSString *cursor = response.cursor;
        BOOL hasMore = [response.hasMore boolValue];

        [self printEntries:entries];

        if (hasMore) {
          NSLog(@"Folder is large enough where we need to call `listFolderContinue:`");

          [self listFolderContinueWithClient:client cursor:cursor];
        } else {
          NSLog(@"List folder complete.");
        }
      } else {
        NSLog(@"%@\n%@\n", routeError, networkError);
      }
    }];

...
...
...

- (void)listFolderContinueWithClient:(DBUserClient *)client cursor:(NSString *)cursor {
  [[client.filesRoutes listFolderContinue:cursor]
      setResponseBlock:^(DBFILESListFolderResult *response, DBFILESListFolderContinueError *routeError,
                         DBRequestError *networkError) {
        if (response) {
          NSArray<DBFILESMetadata *> *entries = response.entries;
          NSString *cursor = response.cursor;
          BOOL hasMore = [response.hasMore boolValue];

          [self printEntries:entries];

          if (hasMore) {
            [self listFolderContinueWithClient:client cursor:cursor];
          } else {
            NSLog(@"List folder complete.");
          }
        } else {
          NSLog(@"%@\n%@\n", routeError, networkError);
        }
      }];
}

- (void)printEntries:(NSArray<DBFILESMetadata *> *)entries {
  for (DBFILESMetadata *entry in entries) {
    if ([entry isKindOfClass:[DBFILESFileMetadata class]]) {
      DBFILESFileMetadata *fileMetadata = (DBFILESFileMetadata *)entry;
      NSLog(@"File data: %@\n", fileMetadata);
    } else if ([entry isKindOfClass:[DBFILESFolderMetadata class]]) {
      DBFILESFolderMetadata *folderMetadata = (DBFILESFolderMetadata *)entry;
      NSLog(@"Folder data: %@\n", folderMetadata);
    } else if ([entry isKindOfClass:[DBFILESDeletedMetadata class]]) {
      DBFILESDeletedMetadata *deletedMetadata = (DBFILESDeletedMetadata *)entry;
      NSLog(@"Deleted data: %@\n", deletedMetadata);
    }
  }
}

-listFolder: and -listFolderContinue:


Upload-style request

NSData *fileData = [@"file data example" dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];

// For overriding on upload
DBFILESWriteMode *mode = [[DBFILESWriteMode alloc] initWithOverwrite];

[[[client.filesRoutes uploadData:@"/test/path/in/Dropbox/account/my_output.txt"
                            mode:mode
                      autorename:@(YES)
                  clientModified:nil
                            mute:@(NO)
                  propertyGroups:nil
                       inputData:fileData]
    setResponseBlock:^(DBFILESFileMetadata *result, DBFILESUploadError *routeError, DBRequestError *networkError) {
      if (result) {
        NSLog(@"%@\n", result);
      } else {
        NSLog(@"%@\n%@\n", routeError, networkError);
      }
    }] setProgressBlock:^(int64_t bytesUploaded, int64_t totalBytesUploaded, int64_t totalBytesExpectedToUploaded) {
  NSLog(@"\n%lld\n%lld\n%lld\n", bytesUploaded, totalBytesUploaded, totalBytesExpectedToUploaded);
}];

-uploadData:mode:autorename:clientModified:mute:inputData:

Here's an example of an advanced upload case for "batch" uploading a large number of files:

NSMutableDictionary<NSURL *, DBFILESCommitInfo *> *uploadFilesUrlsToCommitInfo = [NSMutableDictionary new];
DBFILESCommitInfo *commitInfo = [[DBFILESCommitInfo alloc] initWithPath:@"/output/path/in/Dropbox/file.txt"];
[uploadFilesUrlsToCommitInfo setObject:commitInfo forKey:[NSURL fileURLWithPath:@"/local/path/to/file.txt"]];

[client.filesRoutes batchUploadFiles:uploadFilesUrlsToCommitInfo
    queue:nil
    progressBlock:^(int64_t uploaded, int64_t uploadedTotal, int64_t expectedToUploadTotal) {
      NSLog(@"Uploaded: %lld  UploadedTotal: %lld  ExpectedToUploadTotal: %lld", uploaded, uploadedTotal,
            expectedToUploadTotal);
    }
    responseBlock:^(NSDictionary<NSURL *, DBFILESUploadSessionFinishBatchResultEntry *> *fileUrlsToBatchResultEntries,
                    DBASYNCPollError *finishBatchRouteError, DBRequestError *finishBatchRequestError,
                    NSDictionary<NSURL *, DBRequestError *> *fileUrlsToRequestErrors) {
      if (fileUrlsToBatchResultEntries) {
        NSLog(@"Call to `/upload_session/finish_batch/check` succeeded");
        for (NSURL *clientSideFileUrl in fileUrlsToBatchResultEntries) {
          DBFILESUploadSessionFinishBatchResultEntry *resultEntry = fileUrlsToBatchResultEntries[clientSideFileUrl];
          if ([resultEntry isSuccess]) {
            NSString *dropboxFilePath = resultEntry.success.pathDisplay;
            NSLog(@"File successfully uploaded from %@ on local machine to %@ in Dropbox.",
                  [clientSideFileUrl path], dropboxFilePath);
          } else if ([resultEntry isFailure]) {
            // This particular file was not uploaded successfully, although the other
            // files may have been uploaded successfully. Perhaps implement some retry
            // logic here based on `uploadNetworkError` or `uploadSessionFinishError`
            DBRequestError *uploadNetworkError = fileUrlsToRequestErrors[clientSideFileUrl];
            DBFILESUploadSessionFinishError *uploadSessionFinishError = resultEntry.failure;

            // implement appropriate retry logic
          }
        }
      }

      if (finishBatchRouteError) {
        NSLog(@"Either bug in SDK code, or transient error on Dropbox server");
        NSLog(@"%@", finishBatchRouteError);
      } else if (finishBatchRequestError) {
        NSLog(@"Request error from calling `/upload_session/finish_batch/check`");
        NSLog(@"%@", finishBatchRequestError);
      } else if ([fileUrlsToRequestErrors count] > 0) {
        NSLog(@"Other additional errors (e.g. file doesn't exist client-side, etc.).");
        NSLog(@"%@", fileUrlsToRequestErrors);
      }
    }];

Note: the batchUploadFiles: route method that is used above automatically chunk-uploads large files, something other upload methods in the SDK do not do. Also, with this route, response and progress handlers are passed directly into the route as arguments, and not via the setResponseBlock or setProgressBlock methods.

-batchUploadFiles:queue:progressBlock:responseBlock:


Download-style request

Here's an example for downloading to a file (NSURL):

NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *outputDirectory = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0];
NSURL *outputUrl = [outputDirectory URLByAppendingPathComponent:@"test_file_output.txt"];

[[[client.filesRoutes downloadUrl:@"/test/path/in/Dropbox/account/my_file.txt" overwrite:YES destination:outputUrl]
    setResponseBlock:^(DBFILESFileMetadata *result, DBFILESDownloadError *routeError, DBRequestError *networkError,
                       NSURL *destination) {
      if (result) {
        NSLog(@"%@\n", result);
        NSData *data = [[NSFileManager defaultManager] contentsAtPath:[destination path]];
        NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@\n", dataStr);
      } else {
        NSLog(@"%@\n%@\n", routeError, networkError);
      }
    }] setProgressBlock:^(int64_t bytesDownloaded, int64_t totalBytesDownloaded, int64_t totalBytesExpectedToDownload) {
  NSLog(@"%lld\n%lld\n%lld\n", bytesDownloaded, totalBytesDownloaded, totalBytesExpectedToDownload);
}];

-downloadUrl:rev:overwrite:destination:

Here's an example for downloading straight to memory (NSData):

[[[client.filesRoutes downloadData:@"/test/path/in/Dropbox/account/my_file.txt"]
    setResponseBlock:^(DBFILESFileMetadata *result, DBFILESDownloadError *routeError, DBRequestError *networkError,
                       NSData *fileContents) {
      if (result) {
        NSLog(@"%@\n", result);
        NSString *dataStr = [[NSString alloc] initWithData:fileContents encoding:NSUTF8StringEncoding];
        NSLog(@"%@\n", dataStr);
      } else {
        NSLog(@"%@\n%@\n", routeError, networkError);
      }
    }] setProgressBlock:^(int64_t bytesDownloaded, int64_t totalBytesDownloaded, int64_t totalBytesExpectedToDownload) {
  NSLog(@"%lld\n%lld\n%lld\n", bytesDownloaded, totalBytesDownloaded, totalBytesExpectedToDownload);
}];

-downloadData:


Note about background sessions

Currently, the SDK uses a background NSURLSession to perform all download tasks and some upload tasks (including upload from a file, but not from memory or from a stream). Background sessions use a separate process to handle all data transfers. This is conveneient because when your app enters the background, the download / upload will continue.

However, the timeout periods for a background NSURLSession are virtually unlimited, so if you lose your network connection, the error handler will never be executed. Instead, the process will wait for a restored connection, and then resume from there.

If you're looking for more responsive error feedback in the event of a lost connection, you will want to force all requests onto a foreground NSURLSession. See the example in the network configuration section of the README for how to do this.

To read more, please consult Apple's documentation.

NOTE: You should test all background session behavior on an actual test device and not the Xcode simulator, which has a lot of buggy behavior when it comes to handling background session behavior.

Handling responses and errors

Dropbox API v2 deals largely with two data types: structs and unions. Broadly speaking, most route arguments are struct types and most route errors are union types.

NOTE: In this context, "structs" and "unions" are terms specific to the Dropbox API, and not to any of the languages that are used to query the API, so you should avoid thinking of them in terms of their Objective-C definitions.

Struct types are "traditional" object types, that is, composite types made up of a collection of one or more instance fields. All public instance fields are accessible at runtime, regardless of runtime state.

Union types, on the other hand, represent a single value that can take on multiple value types, depending on state. We capture all of these different type scenarios under one "union object", but that object will exist only as one type at runtime. Each union state type, or tag, may have an associated value (if it doesn't, the union state type is said to be void). Associated value types can either be primitives, structs or unions. Although the Objective-C SDK represents union types as objects with multiple instance fields, at most one instance field is accessible at runtime, depending on the tag state of the union.

For example, the /delete endpoint returns an error, DeleteError, which is a union type. The DeleteError union can take on two different tag states: path_lookup (if there is a problem looking up the path) or path_write (if there is a problem writing -- or in this case deleting -- to the path). Here, both tag states have non-void associated values (of types DBFILESLookupError and DBFILESWriteError, respectively).

In this way, one union object is able to capture a multitude of scenarios, each of which has their own value type.

To properly handle union types, you should call each of the is<TAG_STATE> methods associated with the union. Once you have determined the current tag state of the union, you can then safely access the value associated with that tag state (provided there exists an associated value type, i.e., it's not void). If at run time you attempt to access a union instance field that is not associated with the current tag state, an exception will be thrown. See below:


Route-specific errors

[[client.filesRoutes delete_:@"/test/path/in/Dropbox/account"]
    setResponseBlock:^(DBFILESMetadata *result, DBFILESDeleteError *routeError, DBRequestError *networkError) {
      if (result) {
        NSLog(@"%@\n", result);
      } else {
        // Error is with the route specifically (status code 409)
        if (routeError) {
          if ([routeError isPathLookup]) {
            // Can safely access this field
            DBFILESLookupError *pathLookup = routeError.pathLookup;
            NSLog(@"%@\n", pathLookup);
          } else if ([routeError isPathWrite]) {
            DBFILESWriteError *pathWrite = routeError.pathWrite;
            NSLog(@"%@\n", pathWrite);

            // This would cause a runtime error
            // DBFILESLookupError *pathLookup = routeError.pathLookup;
          }
        }
        NSLog(@"%@\n%@\n", routeError, networkError);
      }
    }];

-delete_:


Generic network request errors

In the case of a network error, regardless of whether the error is specific to the route, a generic DBRequestError type will always be returned, which includes information like Dropbox request ID and HTTP status code.

The DBRequestError type is a special union type which is similar to the standard API v2 union type, but also includes a collection of as<TAG_STATE> methods, each of which returns a new instance of a particular error subtype. As with accessing associated values in regular unions, the as<TAG_STATE> should only be called after the corresponding is<TAG_STATE> method returns true. See below:

[[client.filesRoutes delete_:@"/test/path/in/Dropbox/account"]
    setResponseBlock:^(DBFILESMetadata *result, DBFILESDeleteError *routeError, DBRequestError *networkError) {
      if (result) {
        NSLog(@"%@\n", result);
      } else {
        if (routeError) {
          // see handling above
        }
        // Error not specific to the route (status codes 500, 400, 401, 403, 404, 429)
        else {
          if ([networkError isInternalServerError]) {
            DBRequestInternalServerError *internalServerError = [networkError asInternalServerError];
            NSLog(@"%@\n", internalServerError);
          } else if ([networkError isBadInputError]) {
            DBRequestBadInputError *badInputError = [networkError asBadInputError];
            NSLog(@"%@\n", badInputError);
          } else if ([networkError isAuthError]) {
            DBRequestAuthError *authError = [networkError asAuthError];
            NSLog(@"%@\n", authError);
          } else if ([networkError isAccessError]) {
            DBRequestAccessError *accessError = [networkError asAccessError];
            NSLog(@"%@\n", accessError);
          } else if ([networkError isRateLimitError]) {
            DBRequestRateLimitError *rateLimitError = [networkError asRateLimitError];
            NSLog(@"%@\n", rateLimitError);
          } else if ([networkError isHttpError]) {
            DBRequestHttpError *genericHttpError = [networkError asHttpError];
            NSLog(@"%@\n", genericHttpError);
          } else if ([networkError isClientError]) {
            DBRequestClientError *genericLocalError = [networkError asClientError];
            NSLog(@"%@\n", genericLocalError);
          }
        }
      }
    }];

Response handling edge cases

Some routes return union types as result types, so you should be prepared to handle these results in the same way that you handle union route errors. Please consult the documentation for each endpoint that you use to ensure you are properly handling the route's response type.

A few routes return result types that are datatypes with subtypes, that is, structs that can take on multiple state types like unions.

For example, the /delete endpoint returns a generic Metadata type, which can exist either as a FileMetadata struct, a FolderMetadata struct, or a DeletedMetadata struct. To determine at runtime which subtype the Metadata type exists as, perform an isKindOfClass check for each possible class, and then cast the result accordingly. See below:

[[client.filesRoutes delete_:@"/test/path/in/Dropbox/account"]
    setResponseBlock:^(DBFILESMetadata *result, DBFILESDeleteError *routeError, DBRequestError *networkError) {
      if (result) {
        if ([result isKindOfClass:[DBFILESFileMetadata class]]) {
          DBFILESFileMetadata *fileMetadata = (DBFILESFileMetadata *)result;
          NSLog(@"File data: %@\n", fileMetadata);
        } else if ([result isKindOfClass:[DBFILESFolderMetadata class]]) {
          DBFILESFolderMetadata *folderMetadata = (DBFILESFolderMetadata *)result;
          NSLog(@"Folder data: %@\n", folderMetadata);
        } else if ([result isKindOfClass:[DBFILESDeletedMetadata class]]) {
          DBFILESDeletedMetadata *deletedMetadata = (DBFILESDeletedMetadata *)result;
          NSLog(@"Deleted data: %@\n", deletedMetadata);
        }
      } else {
        if (routeError) {
          // see handling above
        } else {
          // see handling above
        }
      }
    }];

This Metadata object is known as a datatype with subtypes in our API v2 documentation.

Datatypes with subtypes are a way combining structs and unions. Datatypes with subtypes are struct objects that contain a tag, which specifies which subtype the object exists as at runtime. The reason we have this construct, as with unions, is so we can capture a multitude of scenarios with one object.

In the above example, the Metadata type can exists as FileMetadata, FolderMetadata or DeleteMetadata. Each of these types have common instances fields like "name" (the name for the file, folder or deleted type), but also instance fields that are specific to the particular subtype. In order to leverage inheritance, we set a common supertype called Metadata which captures all of the common instance fields, but also has a tag instance field, which specifies which subtype the object currently exists as.

In this way, datatypes with subtypes are a hybrid of structs and unions. Only a few routes return result types like this.


Consistent global error handling

Normally, errors are handled on a request-by-request basis by calling setResponseBlock on the returned request task object. Sometimes, however, it makes more sense to handle errors consistently, based on error type, regardless of the source of the request. For instance, maybe you want to display the same dialog every time there is a /files/list_folder error. Or perhaps every time there is an HTTP auth error, you simply want to log the user out of your application.

To implement these examples, you should have code in your app's setup logic (probably in your app delegate) that looks something like the following:

void (^listFolderGlobalResponseBlock)(DBFILESListFolderError *, DBRequestError *, DBTask *) =
    ^(DBFILESListFolderError *folderError, DBRequestError *networkError, DBTask *restartTask) {
      if (folderError) {
        // Display some dialog relating to this error
      }
    };

void (^networkGlobalResponseBlock)(DBRequestError *, DBTask *) =
    ^(DBRequestError *networkError, DBTask *restartTask) {
      if ([networkError isAuthError]) {
        // log the user out of the app, for instance
        [DBClientsManager unlinkAndResetClients];
      } else if ([networkError isRateLimitError]) {
        // automatically retry after backoff period
        DBRequestRateLimitError *rateLimitError = [networkError asRateLimitError];
        int backOff = [rateLimitError.retryAfter intValue];

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, backOff * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            [restartTask restart];
        });
      }
    };

// one response block per error type to globally handle
[DBGlobalErrorResponseHandler registerRouteErrorResponseBlock:listFolderGlobalResponseBlock
                                               routeErrorType:[DBFILESListFolderError class]];

// only one response block total to handle all network errors
[DBGlobalErrorResponseHandler registerNetworkErrorResponseBlock:networkGlobalResponseBlock];

The SDK allows you to set one response block to handle all generic network errors that aren't route-specific (like an HTTP auth error, or a rate-limit error). The SDK also allows you to set a response block to be executed in the event that a certain error type is returned.

These global response blocks will automatically be executed in addition to the response block that you supply for the specific request.


Customizing network calls

Configure network client

It is possible to configure the networking client used by the SDK to make API requests. You can supply custom fields like a custom user agent or custom delegate queue to manage response handler code.

For instance, you can force the SDK to make all network requests on a foreground session:

iOS
#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

DBTransportDefaultConfig *transportConfig =
    [[DBTransportDefaultConfig alloc] initWithAppKey:@"<APP_KEY>" forceForegroundSession:YES];
[DBClientsManager setupWithTransportConfig:transportConfig];
macOS
#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

DBTransportDefaultConfig *transportConfig =
    [[DBTransportDefaultConfig alloc] initWithAppKey:@"<APP_KEY>" forceForegroundSession:YES];
[DBClientsManager setupWithTransportConfigDesktop:transportConfig];

See the DBTransportDefaultConfig class for all of the different customizable networking parameters.

Specify API call response queue

By default, response/progress handler code runs on the main thread. You can set a custom response queue for each API call that you make via the setResponseBlock method, in the event want your response/progress handler code to run on a different thread:

[[client.filesRoutes listFolder:@""]
    setResponseBlock:^(DBFILESListFolderResult *result, DBFILESListFolderError *routeError, DBRequestError *networkError) {
      if (result) {
        NSLog(@"%@", [NSThread currentThread]); // Output: <NSThread: 0x600000261480>{number = 5, name = (null)}
        NSLog(@"%@", [NSThread mainThread]);    // Output: <NSThread: 0x618000062bc0>{number = 1, name = (null)}
        NSLog(@"%@\n", result);
      }
    } queue:[NSOperationQueue new]]

DBClientsManager class

The Objective-C SDK includes a convenience class, DBClientsManager, for integrating the different functions of the SDK into one class.

Single Dropbox user case

For most apps, it is reasonable to assume that only one Dropbox account (and access token) needs to be managed at a time. In this case, the DBClientsManager flow looks like this:

  • call setupWithAppKey/setupWithAppKeyDesktop (or setupWithTeamAppKey/setupWithTeamAppKeyDesktop) in integrating app's app delegate
  • DBClientsManager class determines whether any access tokens are stored -- if any exist, one token is arbitrarily chosen to use for the authorizedClient / authorizedTeamClient shared instance
  • if no token is found, client of the SDK should call authorizeFromControllerV2/authorizeFromControllerDesktopV2 to initiate the OAuth flow
  • if auth flow is initiated, client of the SDK should call handleRedirectURL (or handleRedirectURLTeam) in integrating app's app delegate to handle auth redirect back into the app and store the retrieved access token
  • DBClientsManager class sets up a DBUserClient (or DBTeamClient) with the particular network configuration as defined by the DBTransportDefaultConfig instance passed in (or a standard configuration, if no config instance was passed when the setupWith... method was called)

The DBUserClient (or DBTeamClient) is then used to make all of the desired API calls.

  • call unlinkAndResetClients to logout Dropbox user and clear all access tokens

Multiple Dropbox user case

For some apps, it is necessary to manage more than one Dropbox account (and access token) at a time. In this case, the DBClientsManager flow looks like this:

  • access token uids are managed by the app that is integrating with the SDK for later lookup
  • call setupWithAppKey/setupWithAppKeyDesktop (or setupWithTeamAppKey/setupWithTeamAppKeyDesktop) in integrating app's app delegate
  • DBClientsManager class determines whether any access tokens are stored -- if any exist, one token is arbitrarily chosen to use for the authorizedClient / authorizedTeamClient shared instance
  • DBClientsManager class also populates authorizedClients / authorizedTeamClients shared dictionary from all tokens stored in keychain, if any exist
  • if no token is found, client of the SDK should call authorizeFromControllerV2/authorizeFromControllerDesktopV2 to initiate the OAuth flow
  • if auth flow is initiated, call handleRedirectURL (or handleRedirectURLTeam) in integrating app's app delegate to handle auth redirect back into the app and store the retrieved access token
  • at this point, the app that is integrating with the SDK should persistently save the tokenUid from the DBAccessToken field of the DBOAuthResult object returned from the handleRedirectURL (or handleRedirectURLTeam) method
  • DBClientsManager class sets up a DBUserClient (or DBTeamClient) with the particular network configuration as defined by the DBTransportDefaultConfig instance passed in (or a standard configuration, if no config instance was passed when the setupWith... method was called) and saves it to the list of authorized clients

The DBUserClients (or DBTeamClients) in authorizedClients / authorizedTeamClients is then used to make all of the desired API calls.

  • call unlinkAndResetClient to logout a particular Dropbox user and clear their access token
  • call unlinkAndResetClients to logout all Dropbox users and clear all access tokens

Examples

Example projects that demonstrate how to integrate your app with the SDK can be found in the Examples/ folder.

  • DBRoulette - Play a fun game of photo roulette with the image files in your Dropbox!

Migrating from API v1

This section contains relevant info for migrating your app from API v1 to API v2 (which should be finished by June 28, 2017, when API v1 will be retired).

For a general API v1 migration guide, please see here.

Migrating OAuth tokens from earlier SDKs

If your app was originally using an earlier API v1 SDK, including the iOS Core SDK, the OS X Core SDK, the iOS Sync SDK, or the OS X Sync SDK, then you can use the v2 SDK to perform a one-time migration of OAuth 1 tokens to OAuth 2.0 tokens, which are used by API v2. That way, when you migrate your app from the earlier SDK to the new API v2 SDK, users will not need to reauthenticate with Dropbox after you perform this update.

To perform this auth token migration, in your app delegate, you should call the following method:

+checkAndPerformV1TokenMigration:queue:appKey:appSecret:

BOOL willPerformMigration = [DBClientsManager checkAndPerformV1TokenMigration:^(BOOL shouldRetry, BOOL invalidAppKeyOrSecret,
                                                    NSArray<NSArray<NSString *> *> *unsuccessfullyMigratedTokenData) {
  if (invalidAppKeyOrSecret) {
    // Developers should ensure that the appropriate app key and secret are being supplied.
    // If your app has multiple app keys / secrets, then run this migration method for
    // each app key / secret combination, and ignore this boolean.
  }

  if (shouldRetry) {
    // Store this BOOL somewhere to retry when network connection has returned
  }

  if ([unsuccessfullyMigratedTokenData count] != 0) {
    NSLog(@"The following tokens were unsucessfully migrated:");
    for (NSArray<NSString *> *tokenData in unsuccessfullyMigratedTokenData) {
      NSLog(@"DropboxUserID: %@, AccessToken: %@, AccessTokenSecret: %@, StoredAppKey: %@", tokenData[0],
            tokenData[1], tokenData[2], tokenData[3]);
    }
  }

  if (!invalidAppKeyOrSecret && !shouldRetry && [unsuccessfullyMigratedTokenData count] == 0) {
    [DBClientsManager setupWithAppKey:@"<APP_KEY>"];
  }
} queue:nil appKey:@"<APP_KEY>" appSecret:@"<APP_SECRET>"];

if (!willPerformMigration) {
  [DBClientsManager setupWithAppKey:@"<APP_KEY>"];
}

This method should successfully migrate all access tokens stored by the official Dropbox API SDKs from approximately 2012 until present, for both iOS and OS X. It will make one call to our OAuth 1 conversion endpoint for each OAuth 1 token that has been stored in your application's keychain by the v1 SDK. The method will execute all network requests off the main thread.

Here, token migration is treated as an atomic operation. Either all tokens that are possible to migrate are migrated at once, or none of them are. If all token conversion requests complete successfully, then the shouldRetry argument in responseBlock will be NO. If some token conversion requests succeed and some fail, and if the failures are for any reason other than network connectivity issues (e.g. token has been invalidated), then the migration will continue normally, and those tokens that were unsuccessfully migrated will be skipped, and shouldRetry will be NO. If any of the failures were because of network connectivity issues, none of the tokens will be migrated, and shouldRetry will be YES.


Documentation


Stone

All of our routes and data types are auto-generated using a framework called Stone.

The stone repo contains all of the Objective-C specific generation logic, and the spec repo contains the language-neutral API endpoint specifications which serve as input to the language-specific generators.


Modifications

If you're interested in modifying the SDK codebase, you should take the following steps:

  • clone this GitHub repository to your local filesystem
  • run git submodule init and then git submodule update
  • navigate to TestObjectiveDropbox and run pod install
  • open TestObjectiveDropbox/TestObjectiveDropbox.xcworkspace in Xcode
  • implement your changes to the SDK source code.

To ensure your changes have not broken any existing functionality, you can run a series of integration tests by following the instructions listed in the ViewController.m file.


Code generation

If you're interested in manually generating the SDK serialization logic, perform the following:

  • clone this GitHub repository to your local filesystem
  • run git submodule init and then git submodule update
  • navigate to the Stone GitHub repo, and install all necessary dependencies
  • run ./generate_base_client.py to generate code

To ensure your changes have not broken any existing functionality, you can run a series of integration tests by following the instructions listed in the ViewController.m file.


App Store Connect Privacy Labels

To assist developers using Dropbox SDKs in filling out Apple’s Privacy Practices Questionnaire, we’ve provided the below information on the data that may be collected and used by Dropbox.

As you complete the questionnaire you should note that the below information is general in nature. Dropbox SDKs are designed to be configured by the developer to incorporate Dropbox functionality as is best suited to their application. As a result of this customizable nature of the Dropbox SDKs, we are unable to provide information on the actual data collection and use for each application. We advise developers reference our Dropbox for HTTP Developers for specifics on how data is collected by each Dropbox API.

In addition, you should note that the information below only identifies Dropbox’s collection and use of data. You are responsible for identifying your own collection and use of data in your app, which may result in different questionnaire answers than identified below:

Data Collected by Dropbox Data Use Data Linked to the User Tracking
Contact Info
 • Name Not collected N/A N/A N/A
 • Email Address May be collected
(if you enable authentication using an email address)
• Application functionality Y N
Health & Fitness Not collected N/A N/A N/A
Financial Info Not collected N/A N/A N/A
Location Not collected N/A N/A N/A
Sensitive Info Not collected N/A N/A N/A
Contacts Not collected N/A N/A N/A
User Content
 • Audio Data May be collected • Application functionality Y N
 • Photos or Videos May be collected • Application functionality Y N
 • Other User Content May be collected • Application functionality Y N
Browsing History Not collected N/A N/A N/A
Search History
 • Search History May be collected
(if using search functionality)
• Application functionality
• Analytics
Y N
Identifiers
 • User ID Collected • Application functionality
• Analytics
Y N
Purchases Not collected N/A N/A N/A
Usage Data
 • Product Interaction Collected • Application functionality
• Analytics
• Product personalization
Y N
Diagnostics
 • Other Diagnostic Data Collected
(API call logs)
• Application functionality Y N
Other Data N/A N/A N/A N/A

Bugs

Please post any bugs to the issue tracker found on the project's GitHub page.

Please include the following with your issue:

  • a description of what is not working right
  • sample code to help replicate the issue

Thank you!

dropbox-sdk-obj-c's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dropbox-sdk-obj-c's Issues

Broken support for iOS 8.4 in a recent update

Now I know why so many users are getting crashes, and Crashlytics isn't picking it up!
Try running your host app with this pod in iOS simulator 8.x ...

dyld: Symbol not found: _NSURLSessionTaskPriorityHigh
  Referenced from: /Users/Tim/Library/Developer/CoreSimulator/Devices/F18A24E9-B7E4-4A61-9CB2-F67BD786EFCB/data/Containers/Bundle/Application/96BFF07D-E764-43B6-8CE1-7DE90B974479/Photobooth.app/Frameworks/ObjectiveDropboxOfficial.framework/ObjectiveDropboxOfficial
  Expected in: /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 8.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework/Foundation
 in /Users/Tim/Library/Developer/CoreSimulator/Devices/F18A24E9-B7E4-4A61-9CB2-F67BD786EFCB/data/Containers/Bundle/Application/96BFF07D-E764-43B6-8CE1-7DE90B974479/Photobooth.app/Frameworks/ObjectiveDropboxOfficial.framework/ObjectiveDropboxOfficial

`client_modified` does not match format error.

Hi, I got an error message when trying to upload a file. It seems something is wrong when converting the NSDate object.

Error in call to API function "files/upload": HTTP header "Dropbox-API-Arg": client_modified: time data '2016-09-08T8:38:19 PMZ' does not match format '%Y-%m-%dT%HM:%SZ'.

listFolderContinue - hasMore issue

I have observed infinite loop when using listFolderContinue - call. In my code, I check for DBFILESListFolderResult instance ".hasMore" which returns YES and then pass the ".cursor" to the next call. The result is that I get the files but then I keep seeing hasMore = YES while "entries" is empty. The cursor value always changes from one call to another.

The expected behaviour was that when there are no more entries to receive, hasMore = NO.

Workaround: Continue only if hasMore = YES and entries is not empty.

This was found on Mac OSX Sierra.

Can't compile with Carthage

Been having this error:

A shell task (/usr/bin/xcrun xcodebuild -project /Users/[some path]/Vendor/Carthage/Checkouts/dropbox-sdk-obj-c/Source/ObjectiveDropboxOfficial.xcodeproj CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES -list) failed with exit code 74: xcodebuild: error: Unable to read project 'ObjectiveDropboxOfficial.xcodeproj' from folder '/Users/[some path]/Vendor/Carthage/Checkouts/dropbox-sdk-obj-c/Source'. Reason: Project /Users/[some path]/Vendor/Carthage/Checkouts/dropbox-sdk-obj-c/Source/ObjectiveDropboxOfficial.xcodeproj cannot be opened because it is missing its project.pbxproj file.

Failed to cancel a file download

Hi all,

I have issue in case when file downloding should be cancelled by user:
NSInvalidArgumentException (reason: '*** -[NSFileManager moveItemAtPath:toPath:error:]: source path is nil’) in
- (DBDownloadUrlTask *)response:response:

It happens in following part of code in your library:

// str 343 
NSError *fileMoveError;
[fileManager moveItemAtPath:[location path] toPath:path error:&fileMoveError];
if (fileMoveError) {
    responseBlock(nil, nil, [[DBError alloc] initAsClientError:fileMoveError], _destination);
    return;
}

I use following method for download: downloadUrl: overwrite: destination:

// Download to NSURL
 DBDownloadUrlTask * downloadUrlTask = [[[client.filesRoutes downloadUrl:path overwrite:YES destination:outputUrl]   response:^(DBFILESFileMetadata *result, DBFILESDownloadError *routeError, DBError *error, NSURL *destination) {
//processing  result    
}] 
progress:^(int64_t bytesDownloaded, int64_t totalBytesDownloaded, int64_t totalBytesExpectedToDownload) {
    NSLog(@"%lld\n%lld\n%lld\n", bytesDownloaded, totalBytesDownloaded, totalBytesExpectedToDownload);
}];

and this one for cancel

// Cancel
[downloadUrlTask  cancel];
downloadUrlTask = nil;

How to check which file is latest local or the one inside dropbox folder?

I am working on project where I have to upload/download files between my iOS device and dropbox folder.

Now I have a file named "Filename.txt" in my iOS app as well as in my dropbox folder.

How do I check which one is latest copy?

In dropbox api v1 there was a property called "revision" = "123" which was number and now that is deprecated and a new property called "rev" = "f30099771a" is there but I can't figure it out how to use that.

Please someone help.

Thanks in advance.

Umbrella header file?

Small improvement suggestion: create a "Dropbox.h" which imports all the necessary header files, using #ifdef to determine platform if necessary.

Example why: I import the header file mentioned in the tutorial, I attempt the tutorial code [[[client.filesRoutes createFolder... to create a folder, and I get a compile error of Property filesRoutes cannot be found in forward class object 'DropboxClient'.

Get name of logged in user

How do I get the name of logged in user. I am trying with below code and I think something is missing implementing completion handler.

DropboxClient *client = [DropboxClientsManager authorizedClient];
client.usersRoutes.getCurrentAccount:^(DBUSERSAccount *account, DBNilObject *error) {
};

Can someone Help?

Thanks,
Pratik

Issue with Single Sign-On for Business Account Trial

When utilizing single sign-on, the authentication view that is displayed does nothing after the continue button is selected(a loading spinner is displayed and the button becomes in active). This occurs when the Dropbox application is not installed on the device forcing the sdk to use the internal authentication method. I have tested my use case with the Dropbox application and I am redirected correctly to my single sign-on provider and authentication is successful. Is this a known issue or not currently supported?

Migrating from v1 to v2 API

I'm working on migrating a Mac project from the v1 to v2 API, and finding it slow going. The API has changed significantly, with the v2 more complex and hard to find documentation and examples.

These are issues I have currently; I'd appreciate advice (and enhancements if missing functionality):

  1. With the v1 API, I used the following call to load metadata only for files that had changed since the previous invocation, using a hash. I don't see any option for that in v2?

    [delegate.restClient loadMetadata:dropboxFolderPath withHash:hash];

  2. The response from the above included a lastModifiedDate property for each item. I see that is available in the debug description of DBFILESMetadata, but I don't see a property for it (either as a string or NSDate).

  3. With v1 I used the following call to get a sharing link, with a flag for a short URL or full URL. I don't see an option for a short URL when calling createSharedLinkWithSettings, or any other way to get one?

    [delegate.restClient loadSharableLinkForFile:dropboxFilePath shortUrl:shortURL];

  4. With v1 I used the following to get a direct link to a document. I don't see an equivalent method in v2, unless that is getTemporaryLink?

    [delegate.restClient loadStreamableURLForFile:dropboxFilePath];

I'd appreciate assistance with these!

Something wrong with repo for stone

The stone item in the repo web view is listed as stone @ aa400c5, and shows a 404 when clicked. Cloning the project in GitHub Desktop shows an uncommitted change (that I didn't do):

- Subproject commit aa400c5a23a2c4822c77c3bcab87703bea4bfa48
+ Subproject commit a69b888936a8a69037cadb72ebb6679efc8d0d8f-dirty

`clientModified` never updates after uploading

Hi, @scobbe. I was trying to use the uploadURL:mode:autorename:clientModified:mute:inputURL method with overwrite mode to upload a file and update the clientModified attribute at the same time. However, it seems the clientModified value never updated. The corresponded DBXFILESFileMetadata object always reports the old value no matter how many times I tried to alter it. Does the SDK cache the value somewhere or does the Dropbox will update the value after a long time? Thanks.

Updated:
After some experiments, I found that this issue happens when trying to upload a exact same file to Dropbox. It seems Dropbox server just ignores the uploaded clientModified parameter if there is already a same copy on the server, and keeps the old clientModified value. Can anyone confirm this?

DBReachability status enumerators conflict with Reachability status enumerators

The fix for #37 does not appear to add prefixes to the actual status values { NotReachable = 0, ReachableViaWiFi, ReachableViaWWAN }. These conflict with the status values in Reachability and cause the build error Redefinition of enumerator.

Maybe change them to { DBNotReachable = 0, DBReachableViaWiFi, DBReachableViaWWAN }?

Thanks!

App always crashes after many requests.

Hi, my app always crashes after using a while. So I tried to find out why and turn out it seems the SDK always causes my app crash after about 660 download requests.

Here's my experiment to produce the crash.

  1. Putting a lot of files in a Dropbox folder.
  2. Use DBFILESRoutes's downloadData: method to download the file one by one.
    After about 660 requests, the app crashes without reason. The app disconnected to Xcode immediately and Xcode does not point out which part causes the error.

I added two NSLog() before and after calling the response method, and the last printed line is always DOWNLOAD BEGIN when the app crashed. I also tried to sleep the app for 2 seconds between each request but it doesn't help at all. However, the requests work again after app relaunch. Maybe something is wrong in the internal cache?

DBDownloadDataTask* task = [client.filesRoutes downloadData:path];
NSLog(@"DOWNLOAD BEGIN");
[task response:^(DBFILESFileMetadata *metadata,
                 DBFILESDownloadError *routeError,
                 DBError *error,
                 NSData *data) {
    NSLog(@"DOWNLOAD END");
}];

My assumption is that, the SDK will cause app crash as long as about 660 requests are made. No matter the time interval between each request.

header file not found

Hi

I tried to follow the guide on how to integrate into my project (using Carthage), but I'm stuck at the import line:
#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>
giving me an error that it cannot be found.

I saw this issue was fixed in 1.1.0 (and I checked the code that is downloaded when running the Carthage update command, and it looks like the above file is marked as public in the project), but it doesn't work.

using another import, like:
#import <ObjectiveDropboxOfficial/DropboxClient.h>
does work.

Thanks,
Yaniv

Crash on macOS 10.10

Apple just rejected an app with the Dropbox SDK, as it crashed when running on macOS 10.10.5.

Your documentation says it supports macOS 10.10+, though the Deployment Target in the project is set to 10.11.

Crash report excerpt:

Exception Type:        EXC_BREAKPOINT (SIGTRAP)
Exception Codes:       0x0000000000000002, 0x0000000000000000

Application Specific Information:
dyld: launch, loading dependent libraries

Dyld Error Message:
  Symbol not found: ___NSArray0__
  Referenced from:     /Applications/zCloud.app/Contents/MacOS/../Frameworks/ObjectiveDropboxOfficial_macOS.framework/Versions/A/ObjectiveDropboxOfficial_macOS (which was built for Mac OS X 10.11)
  Expected in: /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
 in /Applications/zCloud.app/Contents/MacOS/../Frameworks/ObjectiveDropboxOfficial_macOS.framework/Versions/A/ObjectiveDropboxOfficial_macOS

I'll try changing the Deployment Target to 10.10, though I'm not certain that's all that's required.

Failed to download files

Hi, I was trying to download a file but always failed to access the downloaded file contents. I tried downloadURL:overwrite:destination:, it did return a response with correct metadata and no errors. But still, the file at the url it returned never exists. I also tried the downloadData: method. There is no error as well but the returned data is always nil.

It seems the error happens in DBXDelegate's URLSession:downloadTask:didFinishDownloadingToURL: method.

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"1: exists(%d)", location, [[NSFileManager defaultManager] fileExistsAtPath:location.path]);
    NSString *sessionId = [self getSessionId:session];
    NSNumber *taskId = [NSNumber numberWithUnsignedInteger:downloadTask.taskIdentifier];
    [_delegateQueue addOperationWithBlock:^{
        NSLog(@"2: exists(%d)", location, [[NSFileManager defaultManager] fileExistsAtPath:location.path]);
        void (^completionHandler)(NSURL *, NSURLResponse *, NSError *) = _downloadTasks[sessionId][taskId][kCompletionHandlerKey];
        completionHandler(location, downloadTask.response, nil);
        [_downloadTasks[sessionId] removeObjectForKey:taskId];
    }];
}

I added two NSLog() to the method and it prints...

1: exists(1)
2: exists(0)

The downloaded file is gone in ``_delegateQueue's operation block. Once I executed the completion handler without running in the _delegateQueue`. The issue no longer exists, I can receive the downloaded file in my response callback as expected. It seems iOS just deleted the temporary file as soon as exiting the delegate method as discussed here: http://stackoverflow.com/questions/22278407/nsurlsessiondownloadtask-downloads-but-ends-with-error

ObjectiveDropboxOfficial_macOS.h doesn't exist

Hello !

I'm using the SDK for both iOS & macOS. Everything is fine for my iOS target but, on macOS, the ObjectiveDropboxOfficial_macOS.h file is nowhere to be found. I checked every directories and it's not there. The ObjectiveDropboxOfficial pod is at version 1.0.8. Some verbose from pod update:

  - Installing target `ObjectiveDropboxOfficial-OSX` OS X 10.10
  - Installing target `ObjectiveDropboxOfficial-iOS` iOS 8.0

I could include DropboxSDKImportsDesktop.h manually but that's not the point.
I'm using Xcode 8, cocoapods 1.0.1, OSX 10.11.6. Deployment target set to 10.11.

My Podfile is fine (got the macOS & iOS pods referenced), not sure what is going wrong.
Is this a known issue ?

Thank you !
Mathieu.

Don't embed Reachability

This causes duplicate symbols when using Reachability in our main app. Please either make it an explicit dependency, or rename the class to avoid conflicts.

Is this official release?

  1. ObjectiveDropboxOfficial is not found in CocoaPod, only ObjectiveDropbox is available.
  2. No DropboxSDKImports.h for import.
  3. No DropboxClientsManager class or 'setupWithAppKey' api.

I can't use this SDK, please help!

Podspec has wrong pod name

Hi! Thanks for making an obj-c SDK that we can use for API v2!
Looks like you're "released". I followed instructions for Pods, ie. pod 'ObjectiveDropboxOfficial', but there's no pod known with that name. Looks like in your podspec you're still just called ObjectiveDropbox. Perhaps you didn't rename & push to cocoapods repo yet?

Creating a folder that already exists yields unexpected error

Hi again! Hope this isn't too many issues filed in one day :)
When attempting to create a folder that already exists, I was expecting something in the routeError that would allow me to detect this, rather than having to look at error and sniff a 409 statusCode.
Is my expectation reasonable?

[[[client.filesRoutes createFolder:subfolderName] response:^(DBFILESFolderMetadata *result, DBFILESCreateFolderError *routeError, DBError *error) {
... <debug breakpoint>
    (lldb) po result
     nil
    (lldb) po routeError
    {
        ".tag" = path;
    }

    (lldb) po error
    DropboxHttpError[{
        ErrorContent = "path/conflict/folder/..";
        RequestId = 53f8e192682948d93f4f925854e530e8;
        StatusCode = 409;
    }];

Crash on nil progressHandler

I don't use progressHandlers. This crashes currently because the handler block is set as an object in an NSDictionary, which throws an uncaught exception when nil.

I made a pull-request to fix this: #18

Wrong message - 'Please enter a CAPTCHA response'

Reproduce stage:

  1. Enter wrong password or wrong name.
  2. Enter right name and password.
  3. Show the wrong message 'Please enter a CAPTCHA response', but the CAPTCHA not showing.

How can I resolve the issue? - 'Please enter a CAPTCHA response'

Thank you very much!
dropbox

How to share an uploaded file that the server auto-renamed?

Sorry if I overlooked something obvious...
I need to upload a file and then share it. Sounds simple but not when server-renaming is desired & switched on.

Steps:

  1. I upload a file, with the policy that the server should rename if there is a collision (there's a high chance there will be a collision in this app, so renaming is necessary)
  2. In the return of uploadURL, I only get a DBFILESFileMetadata, which doesn't contain what the server actually named the file. So there is no way to determine if the server has renamed the file, so I cannot use the path as input to the share command. I can see DBFILESFileMetadata.id_, but I cannot see how to use id_ to init anything useful.

How can I share a file that i've uploaded that the server renamed?

thx

Why is there an assertion in +[DropboxClientsManager unlinkClients]?

If there're no authorized clients +resetClients is a simple no-op (like it should be), but +unlinkClients crashes due to the failed assertion:

NSAssert(
      [DBOAuthManager sharedOAuthManager] != nil,
      @"Call `[DropboxClient setupWithAppKey]` or `[DropboxClient setupWithTeamAppKey]` before calling this method");

What are the caveats of clearing the empty keychain?

Thanks!

Path expression for the root folder

When trying to list the contents of the root folder, it has to be specified @“” rather than @“/“. If specified @“”, following error occurs
[client.filesRoutes listFolder:@"/"]
Error loading metadata: DropboxBadInputError[{
ErrorContent = "Error in call to API function "files/list_folder": request body: path: Specify the root folder as an empty string rather than as "/".";
StatusCode = 400;
}];

Thought the SDK doesn’t like the leading slash,,, However, to display the contents of folder named ‘test’ under root, if I specify the folder name as ’test’, following error occurs,
[client.filesRoutes listFolder:@“test”]

DropboxBadInputError[{
ErrorContent = "Error in call to API function "files/list_folder": request body: path: ‘test’ did not match pattern '(/(.|[\r\n]))?|(ns:[0-9]+(/.)?)'";
StatusCode = 400;
}];

So, this case I have to specify the folder as ‘/test’. this is confusing but is it working as expected?
In V1, I needed to specify ‘/‘ for the root folder.

Env : Version 1.0.8, iOS, cocoapods

Delete multiple files/folders at once

There's a method in Dropbox API called /delete_batch which is pretty useful for deleting multiple items with just one network request. It would be nice to see this API wrapped into the SDK.

I'm aware that this method alone is not enough and you also need to call /delete_batch/check in order to figure out the current status of a deletion job.

Thank you!

Upload error

Hello to all,
I'm implementing the project within my app. I have a problem during the upload of a file on Dropbox.

This is the code I use

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
    NSString *documentsDir = [paths objectAtIndex:0];
    NSString *pathDB = [documentsDir stringByAppendingPathComponent:@"archive.sql"];

    [Archivio getInitialDataToDisplay:pathDB writeOnfile:YES];

    NSString *path = [documentsDir stringByAppendingPathComponent:@"backup_archive.txt"];

    if ([DropboxClientsManager authorizedClient] != nil || [DropboxClientsManager authorizedTeamClient] != nil) {
        NSData *fileData = [NSData dataWithContentsOfFile:path];
        [[[client.filesRoutes uploadData:@"/archive" inputData:fileData]
          response:^(DBFILESFileMetadata *result, DBFILESUploadError *routeError, DBError *error) {
              if (result) {
                  NSLog(@"%@\n", result);
              } else {
                  NSLog(@"%@\n%@\n", routeError, error);
              }
          }] progress:^(int64_t bytesUploaded, int64_t totalBytesUploaded, int64_t totalBytesExpectedToUploaded) {
              NSLog(@"%lld\n%lld\n%lld\n", bytesUploaded, totalBytesUploaded, totalBytesExpectedToUploaded);
          }];

and that the error I get

   ".tag" = path;
     path =     {
         reason =         {
             ".tag" = conflict;
             conflict =             {
                 ".tag" = folder;
             };
         };
         "upload_session_id" = AAAAAAAAGSOLDdsRysL2YQ;
     };
 }
 DropboxHttpError[{
     ErrorContent = "path/conflict/folder/...";
     RequestId = 32deffe21bf9b202f172de351510e1f3;
     StatusCode = 409;
 }];

How can I fix?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.