Link to home
Start Free TrialLog in
Avatar of danz67
danz67Flag for Italy

asked on

Delphi - DropBox UploadFile

Hi, i have this procedure

procedure TDropbox.UploadFile(LocalPath, RemotePath: string);
const
  URL = 'https://api-content.dropbox.com/1/files/%s/%s';
var
  OAuthRequest: TOAuthRequest;
  HMAC: TOAuthSignatureMethod;
var
  FileStream: TFileStream;
  Ts: TStringList;
begin
  FileStream := TFileStream.Create(LocalPath, fmOpenRead or fmShareDenyNone);
  try
    if RemotePath[1] = '/' then
      Delete(RemotePath, 1, 1);

    OAuthRequest := TOAuthRequest.Create('');
    HMAC := TOAuthSignatureMethod_HMAC_SHA1.Create;
    try
      { TODO -oYou : OAuthRequest does not have a way to set the HTTP method being used.
                     It should support this. You will need to add it. }
      OAuthRequest.HTTPURL := Format(URL, [FRoot, URLEncodeRemotePath(RemotePath)]);
      OAuthRequest.FromConsumerAndToken(FOAuthConsumer, FOAuthToken, '');
      OAuthRequest.Sign_Request(HMAC, FOAuthConsumer, FOAuthToken);

      { TODO -oYou : This will likely fail! I will let you figure out why. :-) OAuth.pas does not
                     generate the request string properly for this call if memory serves me correctly.
                     I have provided some very basic exception handling that will write the error message
                     to C:\DropboxErrorMessage.txt. Look at that message. It will tell you what the problem
                     is!

                     This will fail for the reason above, but also because OAuth.pas only supports HTTP GET
                     method, not PUT which is required here. How do you resolve this? }
      try
        FHTTP.Put(OAuthRequest.HTTPURL + '?' + OAuthRequest.GetString, FileStream);
      except
        on E: EIdHTTPProtocolException do
        begin
          Ts := TStringList.Create;
          try
            Ts.Text := E.ErrorMessage;
            Ts.SaveToFile('C:\DropboxErrorMessage.txt');
          finally
            Ts.Free;
          end;
        end;
      end;
    finally
      HMAC.Free;
      OAuthRequest.Free;
    end;
  finally
    FileStream.Free;
  end;
end;

Open in new window


I have try to upload file
procedure TForm1.Button3Click(Sender: TObject);
var
  FileName    :string;
  LocalPath   :string;
  RemotePath  :string;
begin
  FileName    := 'web.png';
  LocalPath   :=  'C:\Data\Dropbox\web.png';
  RemotePath  := '/';
  FDropbox.UploadFile(LocalPath, RemotePath);
end;

Open in new window


Nothing happens, it off?
Avatar of MichaelStaszewski
MichaelStaszewski
Flag of United States of America image

However... something HAS happened. Look at the exception handling in the code you've posted. That code I sent you in your other post (pasted above) is not intended to be used verbatim in your application. It is eating the exception you're getting intentionally to log it.
From your other question you mention using Google translate. I am not sure which language you speak, but translate my comments (in the TODO block comment above) into the translator. Perhaps that will help.
Avatar of danz67

ASKER

I have find the message

{"error": "Call requires one of the following methods: GET, POST, got PUT!"}
Ok. Good details. Continue to post them with your problems. Comments like...

Nothing happens, it off?

...are completely useless.

Now, refer to your other question here on Experts-Exchange. Remember where I posted my tweaks to OAuth.pas? You need them now. OAuth.pas does not support the PUT method; however, Dropbox upload file API uses PUT. This is a problem. The tweak to TOAuthRequest and TOAuthSignatureMethod_HMAC_SHA1 that I posted earlier will fix this. Try that and carry on. You will hit another error soon if I recall correctly.
Avatar of danz67

ASKER

I understand that OAuth does not support the Put method of Post and Dropbox, but I do not know what exactly.
Should I use the code you gave me in another thread?
If yes, I have to edit the file OAuth.pas?
If yes, I've tried but I get different errors, you can change it?

thanks
Yes, those other modifications are required to be in OAuth.pas. The Method property of TOAuthRequest is to be made in OAuth.pas. Same with the build_signature change OR you can subclass the TOAuthSignatureMethod_HMAC_SHA1 class and add it there. I would suggest the subclass route, but alternatively you can just change build_signature in OAuth.pas.

Please post the errors you are receiving. I am building with XE2, but the changes I sent should be compatible with Delphi 7. If the errors you are receiving are HTTP exceptions then the following is very important. Edit the error text before posting so that you mask the value of oauth_token and oauth_token_secret. Those are your secret API key pair and should be shared with no one. Simply mask their values with some other character before sharing.
Avatar of danz67

ASKER

I too am building with XE2

I'll try tomorrow, now I'm very tired, bye
Avatar of danz67

ASKER

I found 3 errors

{ Set the base string }
    Request.BaseString := IfThen(Request.Method = '', 'GET', Request.Method) + '&' + Parm;

Open in new window


[DCC Error] OAuth.pas(527): E2003 Undeclared identifier: 'IfThen'
[DCC Error] OAuth.pas(527): E2003 Undeclared identifier: 'Method'
[DCC Error] OAuth.pas(527): E2003 Undeclared identifier: 'Method'
IfThen is defined in StrUtils and you have not made the Method change to TOAuthRequest as I mentioned in your other question.
Avatar of danz67

ASKER

I'm sorry but I did not understand what you tell me, tell me again how you can do to fix the errors?
See your other question and my comment where I posted the changes to build_signature.

https://www.experts-exchange.com/questions/27642779/Delphi-DropBox.html

I also mentioned a change that is required to TOAuthRequest.

 property Method: string read FMethod write FMethod; // TOAuthRequest assumes HTTP GET always. Sometimes you need other methods.

Open in new window


And IfThen is a standard Delphi function. It is part of Delphi and declared in StrUtils.
Avatar of danz67

ASKER

I ask forgiveness, I had escaped. I put the property as you said, and I also said StrUtils.
Now I compile without errors.
I then tried to upload a file and run I get an error in txt file

"{"error": "Call requires one of the following methods: GET, POST, got PUT!"}"

At this point I think I'll have to change something in the code I've provided, I have seen in official documentation that Dropbox to upload a file you should use the method POST or PUT

procedure TDropbox.UploadFile(LocalPath, RemotePath: string);
const
  URL = 'https://api-content.dropbox.com/1/files/%s/%s';
var
  OAuthRequest: TOAuthRequest;
  HMAC: TOAuthSignatureMethod;
var
  FileStream: TFileStream;
  Ts: TStringList;
begin
  FileStream := TFileStream.Create(LocalPath, fmOpenRead or fmShareDenyNone);
  try
    if RemotePath[1] = '/' then
      Delete(RemotePath, 1, 1);

    OAuthRequest := TOAuthRequest.Create('');
    HMAC := TYourOAuthSignatureMethod_HMAC_SHA1.Create;
    try
      { TODO -oYou : OAuthRequest does not have a way to set the HTTP method being used.
                     It should support this. You will need to add it. }
      OAuthRequest.HTTPURL := Format(URL, [FRoot, URLEncodeRemotePath(RemotePath)]);
      OAuthRequest.FromConsumerAndToken(FOAuthConsumer, FOAuthToken, '');
      OAuthRequest.Sign_Request(HMAC, FOAuthConsumer, FOAuthToken);

      { TODO -oYou : This will likely fail! I will let you figure out why. :-) OAuth.pas does not
                     generate the request string properly for this call if memory serves me correctly.
                     I have provided some very basic exception handling that will write the error message
                     to C:\DropboxErrorMessage.txt. Look at that message. It will tell you what the problem
                     is!

                     This will fail for the reason above, but also because OAuth.pas only supports HTTP GET
                     method, not PUT which is required here. How do you resolve this? }
      try
        FHTTP.Put(OAuthRequest.HTTPURL + '?' + OAuthRequest.GetString, FileStream);
      except
        on E: EIdHTTPProtocolException do
        begin
          Ts := TStringList.Create;
          try
            Ts.Text := E.ErrorMessage;
            Ts.SaveToFile('C:\DropboxErrorMessage.txt');
          finally
            Ts.Free;
          end;
        end;
      end;
    finally
      HMAC.Free;
      OAuthRequest.Free;
    end;
  finally
    FileStream.Free;
  end;
end;

Open in new window

You need to add the following code to TOAuthRequest as stated earlier. Add a public property named "Method"

 property Method: string read FMethod write FMethod; // TOAuthRequest assumes HTTP GET always. Sometimes you need other methods.

Open in new window

Avatar of danz67

ASKER

This I had already done so, as I told you now compile without any errors. I would like to know what to do during the upload.
Ok, now in your UploadFile routine you need to set the value of TOAuthRequest.Method to "PUT" before calling FromConsumerAndToken and signing the request.
Avatar of danz67

ASKER

Umm, I am not clear how
Avatar of danz67

ASKER

in the official documentation says to use this URL structure

https://api-content.dropbox.com/1/files_put/<root>/<path>?param=val

Open in new window


In your code

https://api-content.dropbox.com/1/files/%s/%s

Open in new window

Avatar of danz67

ASKER

I tried so

    procedure UploadFile(LocalPath, RemotePath, ParamValue: string);

Open in new window


procedure TDropbox.UploadFile(LocalPath, RemotePath, ParamValue: string);
const
  URL = 'https://api-content.dropbox.com/1/files_put/<root>/<path>?param=val';
var
  OAuthRequest: TOAuthRequest;
  HMAC: TOAuthSignatureMethod;
var
  FileStream: TFileStream;
  Ts: TStringList;
begin
  FileStream := TFileStream.Create(LocalPath, fmOpenRead or fmShareDenyNone);
  try
    if RemotePath[1] = '/' then
      Delete(RemotePath, 1, 1);

    OAuthRequest := TOAuthRequest.Create('');
    HMAC := TYourOAuthSignatureMethod_HMAC_SHA1.Create;
    try
      { TODO -oYou : OAuthRequest does not have a way to set the HTTP method being used.
                     It should support this. You will need to add it. }
      OAuthRequest.HTTPURL := Format(URL, [FRoot, URLEncodeRemotePath(RemotePath)]);
      OAuthRequest.FromConsumerAndToken(FOAuthConsumer, FOAuthToken, '');
      OAuthRequest.Sign_Request(HMAC, FOAuthConsumer, FOAuthToken);

      { TODO -oYou : This will likely fail! I will let you figure out why. :-) OAuth.pas does not
                     generate the request string properly for this call if memory serves me correctly.
                     I have provided some very basic exception handling that will write the error message
                     to C:\DropboxErrorMessage.txt. Look at that message. It will tell you what the problem
                     is!

                     This will fail for the reason above, but also because OAuth.pas only supports HTTP GET
                     method, not PUT which is required here. How do you resolve this? }
      try
        FHTTP.Put(OAuthRequest.HTTPURL + '?' + OAuthRequest.GetString, FileStream);
      except
        on E: EIdHTTPProtocolException do
        begin
          Ts := TStringList.Create;
          try
            Ts.Text := E.ErrorMessage;
            Ts.SaveToFile('C:\DropboxErrorMessage.txt');
          finally
            Ts.Free;
          end;
        end;
      end;
    finally
      HMAC.Free;
      OAuthRequest.Free;
    end;
  finally
    FileStream.Free;
  end;
end;

Open in new window


procedure TForm1.Button3Click(Sender: TObject);
var
  ParamValue    :string;
  LocalPath   :string;
  RemotePath  :string;
begin
  ParamValue    := 'web.png';
  LocalPath   :=  'C:\Data\Dropbox\web.png';
  RemotePath  := '/';
  FDropbox.UploadFile(LocalPath, RemotePath, ParamValue);
end;

Open in new window


He returns this error

<html>
<head><title>Dropbox - 5xx</title>
<link href="https://www.dropbox.com/static/css/main.css" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="/static/images/favicon.ico"/>
</head>
<body style="background-color:#fff">
<br/><br/>
<div align="center">
<table><tr><td width="600px">
<center><img id="errorimage" src="/static/images/sickbox.png"/></center>
<div id="errorbox">
<h1>Error</h1>Something went wrong. Don't worry, your files are still safe and the Dropboxers have been notified. Check out our <a href="https://www.dropbox.com/help">Help Center</a> and <a href="http://forums.dropbox.com">forums</a> for help, or head back to <a href="home">home</a>.
</div>
</td></tr></table>
</div>

<script type="text/javascript" src="/static/javascript/external/dropbox-mini.js"></script>
<script>
message = {"fr": "\x3ch1>Error\x3c/h1>Something went wrong. Don't worry, your files are still safe and the Dropboxers have been notified. Check out our \x3ca href=\"https://www.dropbox.com/help\">Help Center\x3c/a> and \x3ca href=\"http://forums.dropbox.com\">forums\x3c/a> for help, or head back to \x3ca href=\"home\">home\x3c/a>.", "de": "\x3ch1>Error\x3c/h1>Something went wrong. Don't worry, your files are still safe and the Dropboxers have been notified. Check out our \x3ca href=\"https://www.dropbox.com/help\">Help Center\x3c/a> and \x3ca href=\"http://forums.dropbox.com\">forums\x3c/a> for help, or head back to \x3ca href=\"home\">home\x3c/a>.", "ja": "\x3ch1>Error\x3c/h1>Something went wrong. Don't worry, your files are still safe and the Dropboxers have been notified. Check out our \x3ca href=\"https://www.dropbox.com/help\">Help Center\x3c/a> and \x3ca href=\"http://forums.dropbox.com\">forums\x3c/a> for help, or head back to \x3ca href=\"home\">home\x3c/a>.", "es": "\x3ch1>Error\x3c/h1>Something went wrong. Don't worry, your files are still safe and the Dropboxers have been notified. Check out our \x3ca href=\"https://www.dropbox.com/help\">Help Center\x3c/a> and \x3ca href=\"http://forums.dropbox.com\">forums\x3c/a> for help, or head back to \x3ca href=\"home\">home\x3c/a>."};
function read_cookie (name) {
	    var nameEQ = name + "=";
	    var ca = document.cookie.split(';');
	    for (var i = 0; i < ca.length; i++) {
		    var c = ca[i];
		    while (c.charAt(0) == ' ') {
                c = c.substring(1, c.length);
            }
		    if (c.indexOf(nameEQ) === 0) {
                return c.substring(nameEQ.length, c.length);
            }
	    }
	    return null;
}
Event.observe(document, 'dom:loaded', function () {
    var locale = read_cookie('locale');
    if (locale) {
       var msg = message[locale];
       if (msg) {
           $('errorbox').update(msg);
       }
    }
});
</script>

Open in new window

Umm, I am not clear how

I do not know how to word it any differently. This is a basic Delphi concept, but you need to create a new public property named "Method" in the TOAuthRequest class.
in the official documentation says to use this URL structure

Yes, I copied the DownloadFile routine when setting up the UploadFile and did not modify the URL properly. You are correct, the UploadFile procedure needs to be modified so that "files_put" is used instead of "files."
He returns this error

This assignment...

RemotePath := '/';

Open in new window


...is not valid. You must specify the destination filename.

RemotePath := 'web.png';

Open in new window


And you also must not specify the leading path delimiter or you could make your code more robust to accept RemotePath containing the leading path delimiter and strip it. This is what I have done in my code to make it easier to use.
Avatar of danz67

ASKER

I just can not get the job done, I tried them all, so I'm sorry but I'm going crazy, I know it is easy for you, but I can not unfortunately.
If you make a last effort, thanks
What are you experiencing though? If you are not getting it to work correctly then you are observing something that indicates failure. What are you observing? There are error message returned with every EIdHTTPProtocolException that is generated. What is the error text? I've been trying to get that across to you for days now. No one is going to be able to assist you in anything you do if you cannot describe what it is you are experiencing in some basic level of detail. I understand you are using Google translate and that English is not your native language, but Delphi is Delphi regardless of what language each of us speaks and I trust you to understand the Delphi terminology and know how to debug and ask for assistance. I am not at your computer and cannot see what you are seeing. I can only point you in the direction that has worked for me and help you when you are stuck, but to do so I need for you to try these things and relay your findings.

Take this error message that you posted earlier for example.

{"error": "Call requires one of the following methods: GET, POST, got PUT!"}

This clearly indicates that PUT was being used for an API call that requires GET or POST. The code I sent you contained a bug that I had overlooked. You found this by looking at the Dropbox API. You noticed that my code had used /files/ instead of the intended /files_put/ to upload a file. It's just a simple task of looking at the error messages. The Dropbox API is good and returns good information when there are problems.
And it was not easy for me to get Dropbox working because there is little out there for us Delphi guys as you have found. It did take many days of digging through the OAuth documentation, the Dropbox documentation, and traditional debugging.

The Dropbox API does allow you to specify locale for several of their API methods. Perhaps it is possible for you to add the additional local parameters to the URL where applicable to return messages in your language. I am not 100% certain that Dropbox translates the error messages, but the locale param is there for some calls and worth investigating. The Dropbox documentation should have this outlined.
Avatar of danz67

ASKER

Dwell a little
Avatar of danz67

ASKER

Ok, now i have this error in txt file

{"error": "Invalid signature. Expected signature base string: PUT&https%3A%2F%2Fapi-content.dropbox.com%2F1%2Ffiles_put%2Fsandbox%2Fweb.png&oauth_consumer_key%xxxxxxxxxxx%26oauth_nonce%xxxxxxxxxxxxxx%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1332922564%26oauth_token%3Dphftyqay32ty9yb%26oauth_version%3D1.0"}

Open in new window

I went back to my old code and see the following. I am unsure if this is an issue with Dropbox or just the way OAuth works, but TOAuthRequest.GetString is to be used when getting the request token and access token. For all other calls it looks like I'm manually constructing the signature base string.

Grabbing the request and access tokens expected the oauth_signature parameter to be alpha sorted in the list of other params like this.

GET: /1/oauth/request_token?oauth_consumer_key=********************&oauth_nonce=37AF080E35BBCD4AF6AED45629772CF0&oauth_signature_method=HMAC-SHA1&oauth_signature=aAXKjOIUPIr6wqLSsq2SOku7ZdQ%3D&oauth_timestamp=1332426936&oauth_version=1.0

Open in new window



For all other calls the oauth_signature param is the last like this.

PUT: /1/files_put/sandbox/settings.ini?overwrite=true&oauth_consumer_key=********************&oauth_nonce=68D2280C329F486BA337245D645F76D8&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1332426954&oauth_token=********************&oauth_version=1.0&oauth_signature=joC%2B7Dk6XP9oPzD7W1cqR%2FXcRaE%3D

Open in new window


I am not sure why this is. So, use TOAuthRequest.GetString when obtaining the request token and access token and replace OAuthRequest.GetString in the download and upload routines to call another routine that looks like...

function TDropbox.GetAuthorizationParameters(OAuthRequest: TOAuthRequest): string;
const
  OAuthParams = 'oauth_consumer_key=%s&' +
    'oauth_nonce=%s&oauth_signature_method=%s&oauth_timestamp=%s&' +
    'oauth_token=%s&oauth_version=%s&oauth_signature=%s';
begin
  Result := Format(OAuthParams, [
      OAuthRequest.Parameters.Values['oauth_consumer_key'],
      OAuthRequest.Parameters.Values['oauth_nonce'],
      OAuthRequest.Parameters.Values['oauth_signature_method'],
      OAuthRequest.Parameters.Values['oauth_timestamp'],
      OAuthRequest.Parameters.Values['oauth_token'],
      OAuthRequest.Parameters.Values['oauth_version'],
      OAuthRequest.Parameters.Values['oauth_signature']]);
end;

Open in new window

Avatar of danz67

ASKER

Ok, i have

FHTTP.Put(OAuthRequest.HTTPURL + '?' + GetAuthorizationParameters(OAuthRequest), FileStream);

Open in new window


Not get this error

{"error": "Invalid signature. Expected signature base string: PUT&https%3A%2F%2Fapi-content.dropbox.com%2F1%2Ffiles_put%2Fsandbox%2Fweb.png&oauth_consumer_key%xxxxxxxxxxxxx%26oauth_nonce%xxxxxxxxxxxxx%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1332949311%26oauth_token%3Dxtx6u3kg6ege50w%26oauth_version%3D1.0"}

Open in new window

Interesting. I don't see anything obviously wrong with the code now. What is the full value of...

OAuthRequest.HTTPURL + '?' + GetAuthorizationParameters(OAuthRequest)

Open in new window


Let's compare that to the error message to be sure that the parameters are in the order as expected which is shown in the error message.
Avatar of danz67

ASKER

The full value in the function is:

($1525830, 'https://api-content.dropbox.com/1/files_put/sandbox/web.png', 'https:/', '/api-content.dropbox.com', '/1/files_put/sandbox/web.png', '', '1.0', 'GET&https%3A%2F%2Fapi-content.dropbox.com%2F1%2Ffiles_put%2Fsandbox%2Fweb.png&oauth_consumer_key%xxxxxxxxxxxx%26oauth_nonce%xxxxxxxxxxxxxxxxx%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1332954971%26oauth_token%3Dhcsu82lxq02ox2d%26oauth_version%3D1.0', 'oauth_consumer_key=xxxxxxxxxxx&oauth_nonce=xxxxxxxxx&oauth_signature_method=HMAC-SHA1&oauth_signature=nq1xMdIoTdB6bo5XyUU1Jh3Qaeg%3D&oauth_timestamp=1332954971&oauth_token=hcsu82lxq02ox2d&oauth_version=1.0', '')

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of MichaelStaszewski
MichaelStaszewski
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of danz67

ASKER

Finally I succeeded.
Should I get my biggest compliment, because you are different from other experts who almost always lead to solutions. You may as well give the solution with code, you made me understand all the steps.

To download, delete, etc. .. The same procedure?
Good. Most excellent. I knew you would get there. I didn't want to hand feed all of the code to you at once without you at least understanding the process and seeing the various errors. Your question had a solution that was a little more involved than most. Hopefully it will help you debug any additional problems you encounter. Remember that the error text from Dropbox will always tell you what the problem is! Sometimes you must just decipher what it is exactly the error text is trying to say.

Yes, the procedure is identical for the other routines. You will need to make tweaks so that OAuthRequest.Method matches the HTTP method you are using (GET, PUT, POST, DELETE) and the URL for the particular API method will differ, but the general process is the same for all of them.

Now, when you get to the point where you want to process return data from Dropbox remember that it is in JSON format. XE2 has a JSON parser (I think) so you can try using that or you can check out SuperObject (Google it). I use SuperObject because my code started in Delphi 2009. I like it, it works well.
Avatar of Paco66
Paco66

Hello,
I Want get the links of the Files Stored in DropBox/Public by my application
So,I am Starting to learn how access to DropBox with Delphi.
Where can I get the File OAuth.pas for Delphi7?

Thanks.