Any plans to add 'Copy Object' support?

May 29, 2008 at 6:33 PM
or maybe someone has already written some code to do this and is willing to share?

THANKS!
Lubos
Jul 8, 2008 at 7:33 PM
Edited Jul 8, 2008 at 7:38 PM

Hello,

Any suggestions and corrections are welcome.

ObjectCopyRequest.cs

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

using System;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;

namespace Affirma.ThreeSharp.Model
{
public class ObjectCopyRequest : Request 
{
public ObjectCopyRequest(String bucketName, String sourceKey, String destinationKey)
{
this.Method = "PUT";

this.BucketName = bucketName;
this.Key = destinationKey;

this.headers.Add("x-amz-copy-source", bucketName + "/" + sourceKey);
}

}
}

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

ObjectCopyResponse.cs

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

using System; using using System;
using System.Collections.Generic;
using System.Text;

namespace Affirma.ThreeSharp.Model
{
public class ObjectCopyResponse : Response
{
}
}

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

add the following code to ThreeSharpQuery.cs

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

public ObjectCopyResponse ObjectCopy(ObjectCopyRequest request)

{

   return Invoke<ObjectCopyResponse>(request);

}

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Jul 23, 2008 at 6:11 PM
I implemented your objects, and it works great but for one issue I found.

When you copy an object using the x-amz-metadata-directive, the OrderedList of headers will not sort properly in ThreeSharpUtils.MakeCanonicalString.  If your metadata keys starts with anything other than 'a', 'b' or 'c', the sort will place the x-amz-metadata-directive key out of order.

For instance, if I add the keys "album" and "season" I get...


x-amz-meta-album:1986
x-amz-metadata-directive:REPLACE
x-amz-meta-seasonid:2006

This will cause a 403 response from the server.  What I did to fix this was to replace the '-' char with a '~' char, and it sorts properly.  I have included my 'hack' below.  I'd be interested to see a more elegent solution.


        public static string MakeCanonicalString(string verb, string bucketName, string key, SortedList queryParams, SortedList headers, string expires)
        {
            StringBuilder buf = new StringBuilder();
            buf.Append(verb);
            buf.Append("\n");

            SortedList interestingHeaders = new SortedList();
            if (headers != null)
            {
                foreach (string header in headers.Keys)
                {
                    string lk = header.ToLower();
                    if (lk.Equals("content-type") ||
                         lk.Equals("content-md5") ||
                         lk.Equals("date") ||
                         lk.StartsWith(AMAZON_HEADER_PREFIX))
                    {
                        //interestingHeaders.Add(lk, headers[header]);
                        interestingHeaders.Add(lk.Replace('-', '~'), headers[header]);
                    }
                }
            }
            //if (interestingHeaders[ALTERNATIVE_DATE_HEADER] != null)
            if (interestingHeaders[ALTERNATIVE_DATE_HEADER.Replace('-', '~')] != null)
            {
                interestingHeaders.Add("date", "");
            }

            // if the expires is non-null, use that for the date field.  this
            // trumps the x-amz-date behavior.
            if (expires != null)
            {
                interestingHeaders.Add("date", expires);
            }

            // these headers require that we still put a new line after them,
            // even if they don't exist.
            {
                string[] newlineHeaders = { "content~type", "content~md5" };
                foreach (string header in newlineHeaders)
                {
                    if (interestingHeaders.IndexOfKey(header) == -1)
                    {
                        interestingHeaders.Add(header, "");
                    }
                }
            }

            // Finally, add all the interesting headers (i.e.: all that startwith x-amz- ;-))
            foreach (string header in interestingHeaders.Keys) {
                //if (header.StartsWith(AMAZON_HEADER_PREFIX))
                if (header.StartsWith(AMAZON_HEADER_PREFIX.Replace('-', '~')))
                {
                    buf.Append(header.Replace('~', '-')).Append(":").Append((interestingHeaders[header] as string).Trim());
                    //buf.Append(header).Append(":").Append((interestingHeaders[header] as string).Trim());
                }
                else
                {
                    buf.Append(interestingHeaders[header]);
                }
                buf.Append("\n");
            }

            // Build the path using the bucket and key
            buf.Append("/");
            if (bucketName != null && !bucketName.Equals(""))
            {
                buf.Append(bucketName);
                buf.Append("/");
            }

            // Append the key (it may be an empty string)
            if (key != null && key.Length != 0)
            {
                buf.Append(key);
            }

            // if there is an acl, logging, or torrent paramter, add them to the string.
            if (queryParams != null)
            {
                if (queryParams.IndexOfKey("acl") != -1)
                {
                    buf.Append("?acl");
                }
                else if (queryParams.IndexOfKey("torrent") != -1)
                {
                    buf.Append("?torrent");
                }
                else if (queryParams.IndexOfKey("logging") != -1)
                {
                    buf.Append("?logging");
                }
            }

            System.Diagnostics.Debug.WriteLine(buf.ToString());
            return buf.ToString();
        }
Jul 25, 2008 at 2:26 PM
Found a more elegant solution.  This is a thread on the Amazon Form which has the details...

http://developer.amazonwebservices.com/connect/thread.jspa?threadID=23365


First, you need to implement a custom Comparer object.



        private class MyStringComparer : IComparer {
            private CompareInfo myComp;
            private CompareOptions myOptions = CompareOptions.None;

            // Constructs a comparer using the specified CompareOptions.
            public MyStringComparer(CompareInfo cmpi, CompareOptions options) {
                myComp = cmpi;
                this.myOptions = options;
            }

            // Compares strings with the CompareOptions specified in the constructor.
            public int Compare(Object a, Object b) {
                if (a == b) return 0;
                if (a == null) return -1;
                if (b == null) return 1;

                String sa = a as String;
                String sb = b as String;
                if (sa != null && sb != null)
                    return myComp.Compare(sa, sb, myOptions);
                throw new ArgumentException("a and b should be strings.");

            }
        }



Then, in the MakeCanonicalString method...




            MyStringComparer comp = new MyStringComparer(CompareInfo.GetCompareInfo(""), CompareOptions.StringSort);
            SortedList interestingHeaders = new SortedList(comp);




Hope this helps...

Coordinator
Aug 11, 2008 at 6:43 PM
Edited Aug 11, 2008 at 6:44 PM

Hi All,

  Thanks for the feedback!  I've added object copy support to Release 1.4, as well as the custom string comparer to fix the metadata ordering issue.  It's demonstrated in the console sample.

Thanks,
Joel Wetzel
Affirma Consulting