Browse Source

ADD: MacCloud/DropBox step-7: Error and Exception handling mechanisms

rich2014 5 months ago
parent
commit
fc2d65af65

+ 306 - 136
plugins/wfx/MacCloud/src/dropbox/udropboxclient.pas

@@ -18,6 +18,21 @@ uses
 
 type
 
+  { TDropBoxResult }
+
+  TDropBoxResult = class
+  public
+    httpResult: TMiniHttpResult;
+    resultMessage: String;
+  end;
+
+  { EDropBoxException }
+
+  EDropBoxException = class( Exception );
+  EDropBoxConflictException = class( EDropBoxException );
+  EDropBoxPermissionException = class( EDropBoxException );
+  EDropBoxRateLimitException = class( EDropBoxException );
+
   { TDropBoxFile }
 
   TDropBoxFile = class
@@ -73,7 +88,7 @@ type
     procedure requestAuthorization;
     procedure waitAuthorizationAndPrompt;
     procedure closePrompt;
-    function requestToken: Boolean;
+    procedure requestToken;
     procedure onRedirect( const url: NSURL );
   public
     constructor Create( const config: TDropBoxConfig );
@@ -116,7 +131,7 @@ type
       const serverPath: String;
       const localPath: String;
       const callback: IDropBoxProgressCallback );
-    function download: Boolean;
+    procedure download;
   end;
 
   { TDropBoxUploadSession }
@@ -133,7 +148,7 @@ type
       const serverPath: String;
       const localPath: String;
       const callback: IDropBoxProgressCallback );
-    function upload: Boolean;
+    procedure upload;
   end;
 
   { TDropBoxCreateFolderSession }
@@ -144,7 +159,7 @@ type
     _path: String;
   public
     constructor Create( const authSession: TDropBoxAuthPKCESession; const path: String );
-    function createFolder: Boolean;
+    procedure createFolder;
   end;
 
   { TDropBoxDeleteSession }
@@ -155,7 +170,7 @@ type
     _path: String;
   public
     constructor Create( const authSession: TDropBoxAuthPKCESession; const path: String );
-    function delete: Boolean;
+    procedure delete;
   end;
 
   { TDropBoxCopyMoveSession }
@@ -168,9 +183,9 @@ type
   public
     constructor Create( const authSession: TDropBoxAuthPKCESession;
       const fromPath: String; const toPath: String );
-    function copyOrMove( const needToMove: Boolean ): Boolean;
-    function copy: Boolean;
-    function move: Boolean;
+    procedure copyOrMove( const needToMove: Boolean );
+    procedure copy;
+    procedure move;
   end;
 
   { TDropBoxClient }
@@ -190,17 +205,18 @@ type
     function  listFolderGetNextFile: TDropBoxFile;
     procedure listFolderEnd;
   public
-    function download(
+    procedure download(
       const serverPath: String;
       const localPath: String;
-      const callback: IDropBoxProgressCallback ): Boolean;
-    function upload(
+      const callback: IDropBoxProgressCallback );
+    procedure upload(
       const serverPath: String;
       const localPath: String;
-      const callback: IDropBoxProgressCallback ): Boolean;
-    function createFolder( const path: String ): Boolean;
-    function delete(  const path: String ): Boolean;
-    function copyOrMove( const fromPath: String; const toPath: String; const needToMove: Boolean ): Boolean;
+      const callback: IDropBoxProgressCallback );
+  public
+    procedure createFolder( const path: String );
+    procedure delete(  const path: String );
+    procedure copyOrMove( const fromPath: String; const toPath: String; const needToMove: Boolean );
   end;
 
 implementation
@@ -221,7 +237,8 @@ type
 
   TDropBoxConstHeader = record
     AUTH: String;
-    ARG: String
+    ARG: String;
+    RESULT: String;
   end;
 
   TDropBoxConst = record
@@ -246,9 +263,79 @@ const
     HEADER: (
       AUTH: 'Authorization';
       ARG: 'Dropbox-API-Arg';
+      RESULT: 'Dropbox-API-Result';
     );
   );
 
+// raise the corresponding exception if there are errors
+procedure DropBoxClientProcessResult( const dropBoxResult: TDropBoxResult );
+var
+  httpResult: TMiniHttpResult;
+  httpError: NSError;
+  httpErrorDescription: String;
+
+  procedure processHttpError;
+  begin
+    if Assigned(httpError) then begin
+      httpErrorDescription:= httpError.localizedDescription.UTF8String;
+      case httpError.code of
+        2: raise EFileNotFoundException.Create( httpErrorDescription );
+        -1001: raise EInOutError.Create( httpErrorDescription );
+      end;
+    end;
+  end;
+
+  procedure processDropBox409Error;
+  begin
+    if dropBoxResult.resultMessage.IndexOf('not_found') >= 0 then
+      raise EFileNotFoundException.Create( dropBoxResult.resultMessage );
+    if dropBoxResult.resultMessage.IndexOf('conflict') >= 0 then
+      raise EDropBoxConflictException.Create( dropBoxResult.resultMessage );
+    raise EDropBoxPermissionException.Create( dropBoxResult.resultMessage );
+  end;
+
+  procedure processDropBoxError;
+  begin
+    if (httpResult.resultCode>=200) and (httpResult.resultCode<=299) then
+      Exit;
+    case httpResult.resultCode of
+      409: processDropBox409Error;
+      403: raise EDropBoxPermissionException.Create( dropBoxResult.resultMessage );
+      429: raise EDropBoxRateLimitException.Create( dropBoxResult.resultMessage );
+      else raise EDropBoxException.Create( dropBoxResult.resultMessage );
+    end;
+  end;
+
+  procedure processError;
+  begin
+    httpResult:= dropBoxResult.httpResult;
+    httpError:= httpResult.error;
+
+    processHttpError;
+    processDropBoxError;
+  end;
+
+  procedure logException( const e: Exception );
+  var
+    message: String;
+  begin
+    message:= 'DropBox Error';
+    if e.Message <> EmptyStr then
+      message:= message + ': ' + e.Message;
+    TLogUtil.log( 6, message );
+  end;
+
+begin
+  try
+    processError;
+  except
+    on e: Exception do begin
+      logException( e );
+      raise;
+    end;
+  end;
+end;
+
 { TDropBoxFile }
 
 function TDropBoxFile.isFolder: Boolean;
@@ -309,10 +396,10 @@ begin
   button.performClick( nil );
 end;
 
-function TDropBoxAuthPKCESession.requestToken: Boolean;
+procedure TDropBoxAuthPKCESession.requestToken;
 var
   http: TMiniHttpClient;
-  requestResult: TMiniClientResult;
+  dropBoxResult: TDropBoxResult;
 
   procedure doRequest;
   var
@@ -324,33 +411,38 @@ var
     queryItems.Add( 'code', _code );
     queryItems.Add( 'code_verifier', _codeVerifier );
     queryItems.Add( 'grant_type', 'authorization_code' );
-    requestResult:= http.post( DropBoxConst.URI.TOKEN, queryItems );
+    dropBoxResult:= TDropBoxResult.Create;
+    dropBoxResult.httpResult:= http.post( DropBoxConst.URI.TOKEN, queryItems );
+    dropBoxResult.resultMessage:= dropBoxResult.httpResult.body;
+
+    DropBoxClientProcessResult( dropBoxResult );
   end;
 
   procedure analyseResult;
   var
     json: NSDictionary;
   begin
-    json:= TJsonUtil.parse( requestResult.responseBody );
+    json:= TJsonUtil.parse( dropBoxResult.httpResult.body );
     _token.access:= TJsonUtil.getString( json, 'access_token' );
     _token.refresh:= TJsonUtil.getString( json, 'refresh_token' );
     _accountID:= TJsonUtil.getString( json, 'account_id' );
   end;
 
 begin
-  Result:= False;
   if _code = EmptyStr then
     Exit;
 
-  http:= TMiniHttpClient.Create;
-  doRequest;
+  try
+    http:= TMiniHttpClient.Create;
+    doRequest;
 
-  if requestResult.statusCode <> 200 then
-    Exit;
-  analyseResult;
-  Result:= True;
-
-  http.Free;
+    if dropBoxResult.httpResult.resultCode <> 200 then
+      Exit;
+    analyseResult;
+  finally
+    FreeAndNil( dropBoxResult );
+    FreeAndNil( http );
+  end;
 end;
 
 procedure TDropBoxAuthPKCESession.onRedirect(const url: NSURL);
@@ -374,10 +466,10 @@ end;
 
 function TDropBoxAuthPKCESession.authorize: Boolean;
 begin
-  Result:= False;
   requestAuthorization;
   TThread.Synchronize( TThread.CurrentThread, @waitAuthorizationAndPrompt );
-  Result:= requestToken;
+  requestToken;
+  Result:= (_token.access <> EmptyStr);
 end;
 
 procedure TDropBoxAuthPKCESession.setAuthHeader(http: TMiniHttpClient);
@@ -392,38 +484,62 @@ end;
 procedure TDropBoxListFolderSession.listFolderFirst;
 var
   http: TMiniHttpClient;
-  requestResult: TMiniClientResult;
+  httpResult: TMiniHttpResult;
+  dropBoxResult: TDropBoxResult;
   body: String;
 begin
-  body:= TJsonUtil.dumps( ['path', _path] );
-  http:= TMiniHttpClient.Create;
-  _authSession.setAuthHeader( http );
-  http.setContentType( HttpConst.ContentType.JSON );
-  http.setBody( body );
-  requestResult:= http.post( DropBoxConst.URI.LIST_FOLDER, nil );
-  if Assigned(_files) then
-    _files.Free;
-  _files:= TDropBoxFiles.Create;
-  if requestResult.statusCode = 200 then
-    analyseListResult( requestResult.responseBody );
-  http.Free;
+  try
+    body:= TJsonUtil.dumps( ['path', _path] );
+    http:= TMiniHttpClient.Create;
+    _authSession.setAuthHeader( http );
+    http.setContentType( HttpConst.ContentType.JSON );
+    http.setBody( body );
+
+    dropBoxResult:= TDropBoxResult.Create;
+    httpResult:= http.post( DropBoxConst.URI.LIST_FOLDER, nil );
+    dropBoxResult.httpResult:= httpResult;
+    dropBoxResult.resultMessage:= httpResult.body;
+
+    if Assigned(_files) then
+      _files.Free;
+    _files:= TDropBoxFiles.Create;
+    if httpResult.resultCode = 200 then
+      analyseListResult( httpResult.body );
+
+    DropBoxClientProcessResult( dropBoxResult );
+  finally
+    FreeAndNil( dropBoxResult );
+    FreeAndNil( http );
+  end;
 end;
 
 procedure TDropBoxListFolderSession.listFolderContinue;
 var
   http: TMiniHttpClient;
-  requestResult: TMiniClientResult;
+  httpResult: TMiniHttpResult;
+  dropBoxResult: TDropBoxResult;
   body: String;
 begin
-  body:= TJsonUtil.dumps( ['cursor', _cursor] );
-  http:= TMiniHttpClient.Create;
-  _authSession.setAuthHeader( http );
-  http.setContentType( HttpConst.ContentType.JSON );
-  http.setBody( body );
-  requestResult:= http.post( DropBoxConst.URI.LIST_FOLDER_CONTINUE, nil );
-  if requestResult.statusCode = 200 then
-    analyseListResult( requestResult.responseBody );
-  http.Free;
+  try
+    body:= TJsonUtil.dumps( ['cursor', _cursor] );
+    http:= TMiniHttpClient.Create;
+    _authSession.setAuthHeader( http );
+    http.setContentType( HttpConst.ContentType.JSON );
+    http.setBody( body );
+
+    dropBoxResult:= TDropBoxResult.Create;
+    httpResult:= http.post( DropBoxConst.URI.LIST_FOLDER_CONTINUE, nil );
+    dropBoxResult.httpResult:= httpResult;
+    dropBoxResult.resultMessage:= httpResult.body;
+
+    if httpResult.resultCode = 200 then
+      analyseListResult( httpResult.body );
+
+    DropBoxClientProcessResult( dropBoxResult );
+  finally
+    FreeAndNil( dropBoxResult );
+    FreeAndNil( http );
+  end;
 end;
 
 procedure TDropBoxListFolderSession.analyseListResult(const jsonString: String);
@@ -507,19 +623,27 @@ begin
   _callback:= callback;
 end;
 
-function TDropBoxDownloadSession.download: Boolean;
+procedure TDropBoxDownloadSession.download;
 var
   http: TMiniHttpClient;
   argJsonString: String;
-  requestResult: TMiniClientResult;
-begin
-  argJsonString:= TJsonUtil.dumps( ['path', _serverPath], True );
-  http:= TMiniHttpClient.Create;
-  _authSession.setAuthHeader( http );
-  http.addHeader( DropBoxConst.HEADER.ARG, argJsonString );
-  requestResult:= http.download( DropBoxConst.URI.DOWNLOAD, _localPath, _callback );
-  Result:= (requestResult.statusCode = 200);
-  http.Free;
+  dropBoxResult: TDropBoxResult;
+begin
+  try
+    argJsonString:= TJsonUtil.dumps( ['path', _serverPath], True );
+    http:= TMiniHttpClient.Create;
+    _authSession.setAuthHeader( http );
+    http.addHeader( DropBoxConst.HEADER.ARG, argJsonString );
+
+    dropBoxResult:= TDropBoxResult.Create;
+    dropBoxResult.httpResult:= http.download( DropBoxConst.URI.DOWNLOAD, _localPath, _callback );
+    dropBoxResult.resultMessage:= dropBoxResult.httpResult.getHeader( DropBoxConst.HEADER.RESULT );
+
+    DropBoxClientProcessResult( dropBoxResult );
+  finally
+    FreeAndNil( dropBoxResult );
+    FreeAndNil( http );
+  end;
 end;
 
 { TDropBoxUploadSession }
@@ -534,19 +658,27 @@ begin
   _callback:= callback;
 end;
 
-function TDropBoxUploadSession.upload: Boolean;
+procedure TDropBoxUploadSession.upload;
 var
   http: TMiniHttpClient;
   argJsonString: String;
-  requestResult: TMiniClientResult;
-begin
-  argJsonString:= TJsonUtil.dumps( ['path', _serverPath], True );
-  http:= TMiniHttpClient.Create;
-  _authSession.setAuthHeader( http );
-  http.addHeader( DropBoxConst.HEADER.ARG, argJsonString );
-  requestResult:= http.upload( DropBoxConst.URI.UPLOAD_SMALL, _localPath, _callback );
-  Result:= (requestResult.statusCode = 200);
-  http.Free;
+  dropBoxResult: TDropBoxResult;
+begin
+  try
+    argJsonString:= TJsonUtil.dumps( ['path', _serverPath], True );
+    http:= TMiniHttpClient.Create;
+    _authSession.setAuthHeader( http );
+    http.addHeader( DropBoxConst.HEADER.ARG, argJsonString );
+
+    dropBoxResult:= TDropBoxResult.Create;
+    dropBoxResult.httpResult:= http.upload( DropBoxConst.URI.UPLOAD_SMALL, _localPath, _callback );
+    dropBoxResult.resultMessage:= dropBoxResult.httpResult.getHeader( DropBoxConst.HEADER.RESULT );
+
+    DropBoxClientProcessResult( dropBoxResult );
+  finally
+    FreeAndNil( dropBoxResult );
+    FreeAndNil( http );
+  end;
 end;
 
 { TDropBoxCreateFolderSession }
@@ -558,20 +690,28 @@ begin
   _path:= path;
 end;
 
-function TDropBoxCreateFolderSession.createFolder: Boolean;
+procedure TDropBoxCreateFolderSession.createFolder;
 var
   http: TMiniHttpClient;
+  dropBoxResult: TDropBoxResult;
   body: String;
-  requestResult: TMiniClientResult;
 begin
-  body:= TJsonUtil.dumps( ['path', _path] );
-  http:= TMiniHttpClient.Create;
-  http.setContentType( HttpConst.ContentType.JSON );
-  _authSession.setAuthHeader( http );
-  http.setBody( body );
-  requestResult:= http.post( DropBoxConst.URI.CREATE_FOLDER, nil );
-  Result:= (requestResult.statusCode = 200);
-  http.Free;
+  try
+    body:= TJsonUtil.dumps( ['path', _path] );
+    http:= TMiniHttpClient.Create;
+    http.setContentType( HttpConst.ContentType.JSON );
+    _authSession.setAuthHeader( http );
+    http.setBody( body );
+
+    dropBoxResult:= TDropBoxResult.Create;
+    dropBoxResult.httpResult:= http.post( DropBoxConst.URI.CREATE_FOLDER, nil );
+    dropBoxResult.resultMessage:= dropBoxResult.httpResult.body;
+
+    DropBoxClientProcessResult( dropBoxResult );
+  finally
+    FreeAndNil( dropBoxResult );
+    FreeAndNil( http );
+  end;
 end;
 
 { TDropBoxDeleteSession }
@@ -583,20 +723,28 @@ begin
   _path:= path;
 end;
 
-function TDropBoxDeleteSession.delete: Boolean;
+procedure TDropBoxDeleteSession.delete;
 var
   http: TMiniHttpClient;
   body: String;
-  requestResult: TMiniClientResult;
-begin
-  body:= TJsonUtil.dumps( ['path', _path] );
-  http:= TMiniHttpClient.Create;
-  http.setContentType( HttpConst.ContentType.JSON );
-  _authSession.setAuthHeader( http );
-  http.setBody( body );
-  requestResult:= http.post( DropBoxConst.URI.DELETE, nil );
-  Result:= (requestResult.statusCode = 200);
-  http.Free;
+  dropBoxResult: TDropBoxResult;
+begin
+  try
+    body:= TJsonUtil.dumps( ['path', _path] );
+    http:= TMiniHttpClient.Create;
+    http.setContentType( HttpConst.ContentType.JSON );
+    _authSession.setAuthHeader( http );
+    http.setBody( body );
+
+    dropBoxResult:= TDropBoxResult.Create;
+    dropBoxResult.httpResult:= http.post( DropBoxConst.URI.DELETE, nil );
+    dropBoxResult.resultMessage:= dropBoxResult.httpResult.body;
+
+    DropBoxClientProcessResult( dropBoxResult );
+  finally
+    FreeAndNil( dropBoxResult );
+    FreeAndNil( http );
+  end;
 end;
 
 { TDropBoxCopyMoveSession }
@@ -609,38 +757,45 @@ begin
   _toPath:= toPath;
 end;
 
-function TDropBoxCopyMoveSession.copyOrMove( const needToMove: Boolean ): Boolean;
+procedure TDropBoxCopyMoveSession.copyOrMove( const needToMove: Boolean );
 var
   uri: String;
   http: TMiniHttpClient;
+  dropBoxResult: TDropBoxResult;
   body: String;
-  requestResult: TMiniClientResult;
 begin
-  http:= TMiniHttpClient.Create;
-  http.setContentType( HttpConst.ContentType.JSON );
-  _authSession.setAuthHeader( http );
+  try
+    http:= TMiniHttpClient.Create;
+    http.setContentType( HttpConst.ContentType.JSON );
+    _authSession.setAuthHeader( http );
+
+    body:= TJsonUtil.dumps( ['from_path', _fromPath, 'to_path', _toPath] );
+    http.setBody( body );
 
-  body:= TJsonUtil.dumps( ['from_path', _fromPath, 'to_path', _toPath] );
-  http.setBody( body );
+    if needToMove then
+      uri:= DropBoxConst.URI.MOVE
+    else
+      uri:= DropBoxConst.URI.COPY;
 
-  if needToMove then
-    uri:= DropBoxConst.URI.MOVE
-  else
-    uri:= DropBoxConst.URI.COPY;
+    dropBoxResult:= TDropBoxResult.Create;
+    dropBoxResult.httpResult:= http.post( uri, nil );
+    dropBoxResult.resultMessage:= dropBoxResult.httpResult.body;
 
-  requestResult:= http.post( uri, nil );
-  Result:= (requestResult.statusCode = 200);
-  http.Free;
+    DropBoxClientProcessResult( dropBoxResult );
+  finally
+    FreeAndNil( dropBoxResult );
+    FreeAndNil( http );
+  end;
 end;
 
-function TDropBoxCopyMoveSession.copy: Boolean;
+procedure TDropBoxCopyMoveSession.copy;
 begin
-  Result:= copyOrMove( False );
+  copyOrMove( False );
 end;
 
-function TDropBoxCopyMoveSession.move: Boolean;
+procedure TDropBoxCopyMoveSession.move;
 begin
-  Result:= copyOrMove( True );
+  copyOrMove( True );
 end;
 
 { TDropBoxClient }
@@ -681,56 +836,71 @@ begin
   FreeAndNil( _listFolderSession );
 end;
 
-function TDropBoxClient.download(
+procedure TDropBoxClient.download(
   const serverPath: String;
   const localPath: String;
-  const callback: IDropBoxProgressCallback ): Boolean;
+  const callback: IDropBoxProgressCallback );
 var
   session: TDropBoxDownloadSession;
 begin
-  session:= TDropBoxDownloadSession.Create( _authSession, serverPath, localPath, callback );
-  Result:= session.download;
-  session.Free;
+  try
+    session:= TDropBoxDownloadSession.Create( _authSession, serverPath, localPath, callback );
+    session.download;
+  finally
+    FreeAndNil( session );
+  end;
 end;
 
-function TDropBoxClient.upload(
+procedure TDropBoxClient.upload(
   const serverPath: String;
   const localPath: String;
-  const callback: IDropBoxProgressCallback): Boolean;
+  const callback: IDropBoxProgressCallback);
 var
   session: TDropBoxUploadSession;
 begin
-  session:= TDropBoxUploadSession.Create( _authSession, serverPath, localPath, callback );
-  Result:= session.upload;
-  session.Free;
+  try
+    session:= TDropBoxUploadSession.Create( _authSession, serverPath, localPath, callback );
+    session.upload;
+  finally
+    FreeAndNil( session );
+  end;
 end;
 
-function TDropBoxClient.createFolder(const path: String): Boolean;
+procedure TDropBoxClient.createFolder(const path: String);
 var
   session: TDropBoxCreateFolderSession;
 begin
-  session:= TDropBoxCreateFolderSession.Create( _authSession, path );
-  Result:= session.createFolder;
-  session.Free;
+  try
+    session:= TDropBoxCreateFolderSession.Create( _authSession, path );
+    session.createFolder;
+  finally
+    FreeAndNil( session );
+  end;
 end;
 
-function TDropBoxClient.delete(const path: String): Boolean;
+procedure TDropBoxClient.delete(const path: String);
 var
   session: TDropBoxDeleteSession;
 begin
-  session:= TDropBoxDeleteSession.Create( _authSession, path );
-  Result:= session.delete;
-  session.Free;
+  try
+    session:= TDropBoxDeleteSession.Create( _authSession, path );
+    session.delete;
+  finally
+    FreeAndNil( session );
+  end;
 end;
 
-function TDropBoxClient.copyOrMove(const fromPath: String; const toPath: String;
-  const needToMove: Boolean ): Boolean;
+procedure TDropBoxClient.copyOrMove(const fromPath: String; const toPath: String;
+  const needToMove: Boolean );
 var
   session: TDropBoxCopyMoveSession;
 begin
-  session:= TDropBoxCopyMoveSession.Create( _authSession, fromPath, toPath );
-  Result:= session.copyOrMove( needToMove );
-  session.Free;
+  try
+    session:= TDropBoxCopyMoveSession.Create( _authSession, fromPath, toPath );
+    session.copyOrMove( needToMove );
+  finally
+    FreeAndNil( session );
+  end;
 end;
 
 end.

+ 101 - 66
plugins/wfx/MacCloud/src/httpclient/uminihttpclient.pas

@@ -50,9 +50,18 @@ type
 
   TQueryItemsDictonary = specialize TDictionary<string, string>;
 
-  TMiniClientResult = class
-    statusCode: Integer;
-    responseBody: UTF8String;
+  { TMiniHttpResult }
+
+  TMiniHttpResult = class
+  public
+    response: NSHTTPURLResponse;
+    error: NSError;
+    resultCode: Integer;
+    body: String;
+  public
+    destructor Destroy; override;
+  public
+    function getHeader( const name: String ): String;
   end;
 
   IMiniHttpDataCallback = interface
@@ -68,7 +77,8 @@ type
     procedure complete( const response: NSURLResponse; const error: NSError ); virtual; abstract;
   end;
 
-  TMiniHttpConnectionState = ( running, canceled, success, fail );
+  {$scopedEnums on}
+  TMiniHttpConnectionState = ( running, aborted, successful, failed );
 
   { TMiniHttpConnectionDataDelegate }
 
@@ -76,10 +86,11 @@ type
   private
     _data: NSMutableData;
     _processor: TMiniHttpDataProcessor;
-    _result: TMiniClientResult;
-    _response: NSURLResponse;
+    _result: TMiniHttpResult;
     _runloop: CFRunLoopRef;
     _state: TMiniHttpConnectionState;
+  private
+    procedure cancelConnection( const connection: NSURLConnection; const name: NSString ); message 'HttpClient_cancelConnection:connection:';
   public
     function init: id; override;
     procedure dealloc; override;
@@ -92,7 +103,7 @@ type
     procedure setProcessor( const processor: TMiniHttpDataProcessor ); message 'HttpClient_setProcessor:';
     function state: TMiniHttpConnectionState; message 'HttpClient_state';
     function isRunning: Boolean; message 'HttpClient_isRunning';
-    function getResult: TMiniClientResult; message 'HttpClient_getResult';
+    function getResult: TMiniHttpResult; message 'HttpClient_getResult';
   end;
 
   { TMiniHttpClient }
@@ -112,11 +123,11 @@ type
   protected
     procedure doRunloop( const delegate: TMiniHttpConnectionDataDelegate );
     function doPost( const urlPart: String; lclItems: TQueryItemsDictonary;
-      const processor: TMiniHttpDataProcessor ): TMiniClientResult;
+      const processor: TMiniHttpDataProcessor ): TMiniHttpResult;
   public
-    function post( const urlPart: String; lclItems: TQueryItemsDictonary ): TMiniClientResult;
-    function download( const urlPart: String; const localPath: String; const callback: IMiniHttpDataCallback ): TMiniClientResult;
-    function upload( const urlPart: String; const localPath: String; const callback: IMiniHttpDataCallback ): TMiniClientResult;
+    function post( const urlPart: String; lclItems: TQueryItemsDictonary ): TMiniHttpResult;
+    function download( const urlPart: String; const localPath: String; const callback: IMiniHttpDataCallback ): TMiniHttpResult;
+    function upload( const urlPart: String; const localPath: String; const callback: IMiniHttpDataCallback ): TMiniHttpResult;
   end;
 
 implementation
@@ -276,14 +287,41 @@ begin
   _stream.close;
 end;
 
+{ TMiniHttpResult }
+
+destructor TMiniHttpResult.Destroy;
+begin
+  if Assigned(self.response) then
+    self.response.release;
+  if Assigned(self.error) then
+    self.error.release;
+end;
+
+function TMiniHttpResult.getHeader(const name: String): String;
+begin
+  if Assigned(self.response) then
+    Result:= NSString( self.response.allHeaderFields.objectForKey( NSSTR(name) ) ).UTF8String
+  else
+    Result:= EmptyStr;
+end;
 
 { TMiniHttpConnectionDataDelegate }
 
+procedure TMiniHttpConnectionDataDelegate.cancelConnection(
+  const connection: NSURLConnection; const name: NSString );
+begin
+  _state:= TMiniHttpConnectionState.aborted;
+  _result.resultCode:= -1;
+  TLogUtil.log( 6, 'HttpClient: Connection Canceled in ' + name.UTF8String );
+  connection.cancel;
+  CFRunLoopStop( _runloop );
+end;
+
 function TMiniHttpConnectionDataDelegate.init: id;
 begin
   Result:= Inherited init;
   _data:= NSMutableData.new;
-  _result:= TMiniClientResult.Create;
+  _result:= TMiniHttpResult.Create;
   _runloop:= CFRunLoopGetCurrent();
   _state:= TMiniHttpConnectionState.running;
 end;
@@ -292,8 +330,6 @@ procedure TMiniHttpConnectionDataDelegate.dealloc;
 begin
   if Assigned(_processor) then
     _processor.Free;
-  if Assigned(_response) then
-    _response.release;
   _data.release;
   _result.Free;
 end;
@@ -301,8 +337,8 @@ end;
 procedure TMiniHttpConnectionDataDelegate.connection_didReceiveResponse(
   connection: NSURLConnection; response: NSURLResponse);
 begin
-  _response:= response;
-  _response.retain;
+  _result.response:= NSHTTPURLResponse( response );
+  response.retain;
 end;
 
 procedure TMiniHttpConnectionDataDelegate.setProcessor( const processor: TMiniHttpDataProcessor );
@@ -320,11 +356,7 @@ begin
 
   ret:= _processor.receive( data, _data, connection );
   if NOT ret then begin
-    _state:= TMiniHttpConnectionState.canceled;
-    _result.statusCode:= -1;
-    connection.cancel;
-    TLogUtil.log( 6, 'HttpClient: Connection Canceled in connection_didReceiveData()' );
-    CFRunLoopStop( _runloop );
+    self.cancelConnection( connection , NSSTR('connection_didReceiveData()') );
   end;
 end;
 
@@ -339,52 +371,45 @@ begin
 
   ret:= _processor.send( totalBytesWritten, connection );
   if NOT ret then begin
-    _state:= TMiniHttpConnectionState.canceled;
-    _result.statusCode:= -1;
-    TLogUtil.log( 6, 'HttpClient: Connection Canceled in connection_didSendBodyData_totalBytesWritten_totalBytesExpectedToWrite()' );
-    connection.cancel;
-    CFRunLoopStop( _runloop );
+    self.cancelConnection( connection , NSSTR('connection_didSendBodyData_totalBytesWritten_totalBytesExpectedToWrite()') );
   end;
 end;
 
 procedure TMiniHttpConnectionDataDelegate.connectionDidFinishLoading
   (connection: NSURLConnection);
 var
-  response: NSHTTPURLResponse;
   dataString: NSString;
 begin
-  _state:= TMiniHttpConnectionState.success;
-  response:= NSHTTPURLResponse( _response );
-  _processor.complete( _response, nil );
-  _result.statusCode:= response.statusCode;
+  _state:= TMiniHttpConnectionState.successful;
+  _processor.complete( _result.response, nil );
+  _result.resultCode:= _result.response.statusCode;
   if (_data.length>0) then begin
     dataString:= NSString.alloc.initWithData_encoding( _data, NSUTF8StringEncoding );
-    _result.responseBody:= dataString.UTF8String;
+    _result.body:= dataString.UTF8String;
     dataString.release;
-  end else begin
-    _result.responseBody:= EmptyStr;
   end;
-  TLogUtil.logInformation( 'Http Client connectionDidFinishLoading' );
-  TLogUtil.logInformation( 'statusCode=' + IntToStr(response.statusCode) );
-  TLogUtil.logInformation( 'responseString=' + _result.responseBody );
+  TLogUtil.logInformation( 'HttpClient connectionDidFinishLoading' );
+  TLogUtil.logInformation( 'resultCode=' + IntToStr(_result.response.statusCode) );
+  TLogUtil.logInformation( 'body=' + _result.body );
   CFRunLoopStop( _runloop );
 end;
 
 procedure TMiniHttpConnectionDataDelegate.connection_didFailWithError(
   connection: NSURLConnection; error: NSError);
 begin
-  _state:= TMiniHttpConnectionState.fail;
-  _processor.complete( _response, error );
+  _state:= TMiniHttpConnectionState.failed;
+  _processor.complete( _result.response, error );
 
-  TLogUtil.log( 6, 'Http Client connection_didFailWithError !!!' );
+  TLogUtil.log( 6, 'HttpClient connection_didFailWithError !!!' );
   if Assigned(error) then begin
     TLogUtil.log( 6, 'error.code=' + IntToStr(error.code) );
     TLogUtil.log( 6, 'error.domain=' + error.domain.UTF8String );
     TLogUtil.log( 6, 'error.description=' + error.description.UTF8String );
   end;
 
-  _result.statusCode:= -1;
-  _result.responseBody:= error.localizedDescription.UTF8String;
+  _result.resultCode:= -1;
+  _result.error:= error;
+  error.retain;
 end;
 
 function TMiniHttpConnectionDataDelegate.state: TMiniHttpConnectionState;
@@ -397,7 +422,7 @@ begin
   Result:= (_state = TMiniHttpConnectionState.running);
 end;
 
-function TMiniHttpConnectionDataDelegate.getResult: TMiniClientResult;
+function TMiniHttpConnectionDataDelegate.getResult: TMiniHttpResult;
 begin
   Result:= _result;
 end;
@@ -449,19 +474,26 @@ procedure TMiniHttpClient.doRunloop(
 var
   connection: NSURLConnection;
 begin
-  connection:= NSURLConnection.alloc.initWithRequest_delegate(
-    _request, delegate );
-  connection.start;
-  repeat
-    CFRunLoopRun;
-  until NOT delegate.isRunning;
-  connection.release;
+  try
+    TLogUtil.logInformation( 'HttpClient start:' + _request.description.UTF8String );
+    connection:= NSURLConnection.alloc.initWithRequest_delegate(
+      _request, delegate );
+    connection.start;
+    repeat
+      CFRunLoopRun;
+    until NOT delegate.isRunning;
+  finally
+    connection.release;
+  end;
+
+  if delegate.state = TMiniHttpConnectionState.aborted then
+    raise EAbort.Create( 'Raised by HttpClient' );
 end;
 
 function TMiniHttpClient.doPost(
   const urlPart: String;
   lclItems: TQueryItemsDictonary;
-  const processor: TMiniHttpDataProcessor): TMiniClientResult;
+  const processor: TMiniHttpDataProcessor): TMiniHttpResult;
 var
   url: NSURL;
   delegate: TMiniHttpConnectionDataDelegate;
@@ -479,22 +511,25 @@ var
   end;
 
 begin
-  url:= NSURL.URLWithString( StringToNSString(urlPart) );
-  _request.setURL( url );
-  if lclItems <> nil then begin
-    self.setBody( getBody );
-    self.setContentType( HttpConst.ContentType.UrlEncoded );
+  try
+    url:= NSURL.URLWithString( StringToNSString(urlPart) );
+    _request.setURL( url );
+    if lclItems <> nil then begin
+      self.setBody( getBody );
+      self.setContentType( HttpConst.ContentType.UrlEncoded );
+    end;
+
+    delegate:= TMiniHttpConnectionDataDelegate.new;
+    delegate.setProcessor( processor );
+    doRunloop( delegate );
+  finally
+    Result:= delegate.getResult;
+    FreeAndNil( lclItems );
+    delegate.autorelease;
   end;
-
-  delegate:= TMiniHttpConnectionDataDelegate.new;
-  delegate.setProcessor( processor );
-  doRunloop( delegate );
-  Result:= delegate.getResult;
-  FreeAndNil( lclItems );
-  delegate.autorelease;
 end;
 
-function TMiniHttpClient.post( const urlPart: String; lclItems: TQueryItemsDictonary ): TMiniClientResult;
+function TMiniHttpClient.post( const urlPart: String; lclItems: TQueryItemsDictonary ): TMiniHttpResult;
 var
   processor: TMiniHttpDataProcessor;
 begin
@@ -505,7 +540,7 @@ end;
 function TMiniHttpClient.download(
   const urlPart: String;
   const localPath: String;
-  const callback: IMiniHttpDataCallback ): TMiniClientResult;
+  const callback: IMiniHttpDataCallback ): TMiniHttpResult;
 var
   processor: TMiniHttpDataProcessor;
 begin
@@ -518,7 +553,7 @@ end;
 function TMiniHttpClient.upload(
   const urlPart: String;
   const localPath: String;
-  const callback: IMiniHttpDataCallback): TMiniClientResult;
+  const callback: IMiniHttpDataCallback): TMiniHttpResult;
 var
   processor: TMiniHttpUploadProcessor;
 begin

+ 148 - 75
plugins/wfx/MacCloud/src/maccloudfunc.pas

@@ -87,11 +87,24 @@ begin
   LogProc( PluginNumber, MsgType, buffer );
 end;
 
+function exceptionToResult( const e: Exception ): Integer;
+begin
+  TLogUtil.log( msgtype_importanterror, e.ClassName + ': ' + e.Message );
+
+  if e is EAbort then
+    Result:= FS_FILE_USERABORT
+  else if e is EFileNotFoundException then
+    Result:= FS_FILE_NOTFOUND
+  else if e is EInOutError then
+    Result:= FS_FILE_WRITEERROR
+  else
+    Result:= FS_FILE_NOTSUPPORTED;
+end;
+
 function FsInitW(PluginNr:integer;pProgressProc:tProgressProcW;pLogProc:tLogProcW;
                 pRequestProc:tRequestProcW):integer; cdecl;
 var
   config: TDropBoxConfig;
-  ret: Boolean;
 begin
   PluginNumber:= PluginNr;
   ProgressProc:= pProgressProc;
@@ -102,7 +115,12 @@ begin
 
   config:= TDropBoxConfig.Create( '10mwuvryt76yise', 'dc-10mwuvryt76yise://dropbox/auth' );
   client:= TDropBoxClient.Create( config );
-  ret:= client.authorize;
+  try
+    client.authorize;
+  except
+    on e: Exception do
+      exceptionToResult( e );
+  end;
   Result:= 0
 end;
 
@@ -190,36 +208,50 @@ var
   totalBytes: Integer;
   li: ULARGE_INTEGER;
   exits: Boolean;
-  ret: Boolean;
+
+  function doGetFile: Integer;
+  begin
+    serverPath:= TStringUtil.widecharsToString( RemoteName );
+    localPath:= TStringUtil.widecharsToString( LocalName );
+    li.LowPart:= RemoteInfo^.SizeLow;
+    li.HighPart:= RemoteInfo^.SizeHigh;
+    totalBytes:= li.QuadPart;
+    exits:= TFileUtil.exists( localPath );
+
+    TLogUtil.logInformation(
+      'FsGetFileW: remote=' + serverPath + ', local=' + localPath +
+      ', CopyFlags=' + IntToStr(CopyFlags) + ', size=' + IntToStr(totalBytes) +
+      ', LocalFileExists=' + BoolToStr(exits,True) );
+
+    if (CopyFlags and FS_COPYFLAGS_RESUME <> 0) then
+      Exit( FS_FILE_NOTSUPPORTED );
+    if exits and (CopyFlags and FS_COPYFLAGS_OVERWRITE = 0) then
+      Exit( FS_FILE_EXISTS );
+
+    callback:= TProgressCallback.Create(
+      ProgressProc,
+      PluginNumber,
+      RemoteName,
+      LocalName,
+      totalBytes );
+
+    try
+      callback.progress( 0 );
+      client.download( serverPath, localPath, callback );
+    finally
+      callback.Free;
+    end;
+
+    Result:= FS_FILE_OK;
+  end;
+
 begin
-  serverPath:= TStringUtil.widecharsToString( RemoteName );
-  localPath:= TStringUtil.widecharsToString( LocalName );
-  li.LowPart:= RemoteInfo^.SizeLow;
-  li.HighPart:= RemoteInfo^.SizeHigh;
-  totalBytes:= li.QuadPart;
-  exits:= TFileUtil.exists( localPath );
-
-  TLogUtil.logInformation(
-    'FsGetFileW: remote=' + serverPath + ', local=' + localPath +
-    ', CopyFlags=' + IntToStr(CopyFlags) + ', size=' + IntToStr(totalBytes) +
-    ', LocalFileExists=' + BoolToStr(exits,True) );
-
-  if (CopyFlags and FS_COPYFLAGS_RESUME <> 0) then
-    Exit( FS_FILE_NOTSUPPORTED );
-  if exits and (CopyFlags and FS_COPYFLAGS_OVERWRITE = 0) then
-    Exit( FS_FILE_EXISTS );
-
-  callback:= TProgressCallback.Create(
-    ProgressProc,
-    PluginNumber,
-    RemoteName,
-    LocalName,
-    totalBytes );
-
-  callback.progress( 0 );
-  ret:= client.download( serverPath, localPath, callback );
-  callback.Free;
-  Result:= FS_FILE_OK;
+  try
+    Result:= doGetFile;
+  except
+    on e: Exception do
+      Result:= exceptionToResult( e );
+  end;
 end;
 
 function FsPutFileW(LocalName,RemoteName:pwidechar;CopyFlags:integer):integer; cdecl;
@@ -232,57 +264,96 @@ var
   totalBytes: Integer;
   exits: Boolean;
   ret: Boolean;
+
+  function doPutFile: Integer;
+  begin
+    serverPath:= TStringUtil.widecharsToString( RemoteName );
+    localPath:= TStringUtil.widecharsToString( LocalName );
+    totalBytes:= TFileUtil.filesize( localPath );
+    exits:= (CopyFlags and FS_EXISTS <> 0);
+
+    TLogUtil.logInformation(
+      'FsPutFileW: remote=' + serverPath + ', local=' + localPath +
+      ', CopyFlags=' + IntToStr(CopyFlags) + ', size=' + IntToStr(totalBytes) +
+      ', RemoteFileExists=' + BoolToStr(exits,True) );
+
+    if (CopyFlags and FS_COPYFLAGS_RESUME <> 0) then
+      Exit( FS_FILE_NOTSUPPORTED );
+    if exits and (CopyFlags and FS_COPYFLAGS_OVERWRITE = 0) then
+      Exit( FS_FILE_EXISTS );
+
+    callback:= TProgressCallback.Create(
+      ProgressProc,
+      PluginNumber,
+      RemoteName,
+      LocalName,
+      totalBytes );
+
+    try
+      callback.progress( 0 );
+      client.upload( serverPath, localPath, callback );
+    finally
+      callback.Free;
+    end;
+
+    Result:= FS_FILE_OK;
+  end;
+
 begin
-  serverPath:= TStringUtil.widecharsToString( RemoteName );
-  localPath:= TStringUtil.widecharsToString( LocalName );
-  totalBytes:= TFileUtil.filesize( localPath );
-  exits:= (CopyFlags and FS_EXISTS <> 0);
-
-  TLogUtil.logInformation(
-    'FsPutFileW: remote=' + serverPath + ', local=' + localPath +
-    ', CopyFlags=' + IntToStr(CopyFlags) + ', size=' + IntToStr(totalBytes) +
-    ', RemoteFileExists=' + BoolToStr(exits,True) );
-
-  if (CopyFlags and FS_COPYFLAGS_RESUME <> 0) then
-    Exit( FS_FILE_NOTSUPPORTED );
-  if exits and (CopyFlags and FS_COPYFLAGS_OVERWRITE = 0) then
-    Exit( FS_FILE_EXISTS );
-
-  callback:= TProgressCallback.Create(
-    ProgressProc,
-    PluginNumber,
-    RemoteName,
-    LocalName,
-    totalBytes );
-
-  callback.progress( 0 );
-  ret:= client.upload( serverPath, localPath, callback );
-  callback.Free;
-  Result:= FS_FILE_OK;
+  try
+    Result:= doPutFile;
+  except
+    on e: Exception do
+      Result:= exceptionToResult( e );
+  end;
 end;
 
 function FsMkDirW(RemoteDir: pwidechar): bool; cdecl;
 var
   path: String;
 begin
-  path:= TStringUtil.widecharsToString( RemoteDir );
-  Result:= client.createFolder( path );
+  try
+    path:= TStringUtil.widecharsToString( RemoteDir );
+    client.createFolder( path );
+    Result:= True;
+  except
+    on e: Exception do begin
+      exceptionToResult( e );
+      Result:= False;
+    end;
+  end;
 end;
 
 function FsDeleteFileW(RemoteName: pwidechar): bool; cdecl;
 var
   path: String;
 begin
-  path:= TStringUtil.widecharsToString( RemoteName );
-  Result:= client.delete( path );
+  try
+    path:= TStringUtil.widecharsToString( RemoteName );
+    client.delete( path );
+    Result:= True;
+  except
+    on e: Exception do begin
+      exceptionToResult( e );
+      Result:= False;
+    end;
+  end;
 end;
 
 function FsRemoveDirW(RemoteName: pwidechar): bool; cdecl;
 var
   path: String;
 begin
-  path:= TStringUtil.widecharsToString( RemoteName );
-  Result:= client.delete( path );
+  try
+    path:= TStringUtil.widecharsToString( RemoteName );
+    client.delete( path );
+    Result:= True;
+  except
+    on e: Exception do begin
+      exceptionToResult( e );
+      Result:= False;
+    end;
+  end;
 end;
 
 function FsRenMovFileW(OldName, NewName: pwidechar; Move, OverWrite: bool;
@@ -292,19 +363,21 @@ var
   newUtf8Path: String;
   ret: Boolean;
 begin
-  oldUtf8Path:= TStringUtil.widecharsToString( OldName );
-  newUtf8Path:= TStringUtil.widecharsToString( NewName );
-
-  ret:= ProgressProc( PluginNumber, oldName, newName, 0 ) = 0;
-  if ret then begin
-    ret:= client.copyOrMove( oldUtf8Path, newUtf8Path, Move );
-    ProgressProc( PluginNumber, oldName, newName, 100 );
+  try
+    oldUtf8Path:= TStringUtil.widecharsToString( OldName );
+    newUtf8Path:= TStringUtil.widecharsToString( NewName );
+
+    ret:= ProgressProc( PluginNumber, oldName, newName, 0 ) = 0;
+    if ret then begin
+      client.copyOrMove( oldUtf8Path, newUtf8Path, Move );
+      ProgressProc( PluginNumber, oldName, newName, 100 );
+      Result:= FS_FILE_OK
+    end else
+      Result:= FS_FILE_USERABORT;
+  except
+    on e: Exception do
+      Result:= exceptionToResult( e );
   end;
-
-  if ret then
-    Result:= FS_FILE_OK
-  else
-    Result:= FS_FILE_NOTSUPPORTED;
 end;
 
 function FsGetBackgroundFlags:integer; cdecl;