Can't download complete image file from skydri

2019-04-16 03:19发布

问题:

I'm working on a quick wrapper for the skydrive API in C#, but running into issues with downloading a file. For the first part of the file, everything comes through fine, but then there start to be differences in the file and shortly thereafter everything becomes null. I'm fairly sure that it's just me not reading the stream correctly.

This is the code I'm using to download the file:

public const string ApiVersion = "v5.0";
public const string BaseUrl = "https://apis.live.net/" + ApiVersion + "/";

public SkyDriveFile DownloadFile(SkyDriveFile file)
{
    string uri = BaseUrl + file.ID + "/content";
    byte[] contents = GetResponse(uri);
    file.Contents = contents;
    return file;
}

public byte[] GetResponse(string url)
{
    checkToken();
    Uri requestUri = new Uri(url + "?access_token=" + HttpUtility.UrlEncode(token.AccessToken));
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
    request.Method = WebRequestMethods.Http.Get;
    WebResponse response = request.GetResponse();
    Stream responseStream = response.GetResponseStream();
    byte[] contents = new byte[response.ContentLength];
    responseStream.Read(contents, 0, (int)response.ContentLength);
    return contents;
}

This is the image file I'm trying to download

And this is the image I am getting

These two images lead me to believe that I'm not waiting for the response to finish coming through, because the content-length is the same as the size of the image I'm expecting, but I'm not sure how to make my code wait for the entire response to come through or even really if that's the approach I need to take.

Here's my test code in case it's helpful

[TestMethod]
public void CanUploadAndDownloadFile()
{
    var api = GetApi();
    SkyDriveFolder folder = api.CreateFolder(null, "TestFolder", "Test Folder");
    SkyDriveFile file = api.UploadFile(folder, TestImageFile, "TestImage.png");
    file = api.DownloadFile(file);
    api.DeleteFolder(folder);
    byte[] contents = new byte[new FileInfo(TestImageFile).Length];
    using (FileStream fstream = new FileStream(TestImageFile, FileMode.Open))
    {
        fstream.Read(contents, 0, contents.Length);
    }
    using (FileStream fstream = new FileStream(TestImageFile + "2", FileMode.CreateNew))
    {
        fstream.Write(file.Contents, 0, file.Contents.Length);
    }
    Assert.AreEqual(contents.Length, file.Contents.Length);
    bool sameData = true;
    for (int i = 0; i < contents.Length && sameData; i++)
    {
        sameData = contents[i] == file.Contents[i];
    }
    Assert.IsTrue(sameData);
}

It fails at Assert.IsTrue(sameData);

回答1:

This is because you don't check the return value of responseStream.Read(contents, 0, (int)response.ContentLength);. Read doesn't ensure that it will read response.ContentLength bytes. Instead it returns the number of bytes read. You can use a loop or stream.CopyTo there.

Something like this:

WebResponse response = request.GetResponse();
MemoryStream m = new MemoryStream();
response.GetResponseStream().CopyTo(m);
byte[] contents = m.ToArray();


回答2:

As LB already said, you need to continue to call Read() until you have read the entire stream.

Although Stream.CopyTo will copy the entire stream it does not ensure that read the number of bytes expected. The following method will solve this and raise an IOException if it does not read the length specified...

    public static void Copy(Stream input, Stream output, long length)
    {
        byte[] bytes = new byte[65536];
        long bytesRead = 0;
        int len = 0;
        while (0 != (len = input.Read(bytes, 0, Math.Min(bytes.Length, (int)Math.Min(int.MaxValue, length - bytesRead)))))
        {
            output.Write(bytes, 0, len);
            bytesRead = bytesRead + len;
        }
        output.Flush();
        if (bytesRead != length)
            throw new IOException();
    }