2016-05-25 6 views
2

大きなファイルをOpenStackプロバイダにアップロードする方法を検討中です。 OpenStackは通常、1ファイルにつき5GBの制限がありますが、これはセグメントでファイルをアップロードしてからマニフェストを追加することで回避できます。既に開いているFileStreamのセグメントをStreamパラメータに渡す

http://docs.openstack.org/developer/swift/overview_large_objects.htmlによると、以下のようにこの問題が発生した:

# First, upload the segments 
curl -X PUT -H 'X-Auth-Token: <token>'   http://<storage_url>/container/myobject/00000001 --data-binary '1' 
curl -X PUT -H 'X-Auth-Token: <token>'   http://<storage_url>/container/myobject/00000002 --data-binary '2' 
curl -X PUT -H 'X-Auth-Token: <token>'   http://<storage_url>/container/myobject/00000003 --data-binary '3' 

# Next, create the manifest file 
curl -X PUT -H 'X-Auth-Token: <token>'   -H 'X-Object-Manifest: container/myobject/'   http://<storage_url>/container/myobject --data-binary '' 

# And now we can download the segments as a single object 
curl -H 'X-Auth-Token: <token>'   http://<storage_url>/container/myobject 

私はC#で最初の部分を作成しようとしているが、私はオープン/クローズ/再開いたFileStreamを防ぎたいです。これは、ファイルの実際のアップロードのためにHttpClient.PutAsyncを使用しているので(そして使用し続けたいと思うので)、問題があります。

using (var client = new HttpClient()) 
{ 
    client.Timeout = Timeout.InfiniteTimeSpan; 
    client.DefaultRequestHeaders.Add("X-Auth-Token", "SomeToken"); 

    using (var fs = File.Open(localFilePathAbs, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite)) 
    using (var bs = new System.IO.BufferedStream(fs, 17000000)) 
    { 
     var response = client.PutAsync(url, new StreamContent(bs), cancellationToken).Result; 
     return new XauthResponse<string> { Content = Encoding.UTF8.GetString(response.Content.ReadAsByteArrayAsync().Result), StatusCode = response.StatusCode }; 
    } 
} 

ので、この作業を取得するために、私はHttpClient.PutAsyncにストリームを渡す必要があるまでストリームすでにオープンを読み続ける:このようになりますセグメント化されたアップロードを、必要としないファイルの場合最大量。

私が持っているコードは次のとおりです。

private static void PutSegmented(string url, string localFilePathAbs, long segmentSize, CancellationToken cancellationToken) 
{ 
    if (url == null) throw new ArgumentNullException("url"); 
    if (localFilePathAbs ==null) throw new ArgumentNullException("localFilePathAbs"); 
    if (segmentSize == 0) throw new ArgumentNullException("segmentSize"); 

    var fileSize = new FileInfo(localFilePathAbs).Length; 

    // The number of parts we'll have to upload 
    var parts = Convert.ToInt64(Math.Ceiling(((double)fileSize)/segmentSize)); 

    // Open the file to upload, use a BufferedStream of roughly 16 megabytes 
    using (var fs = File.Open(localFilePathAbs, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite)) 
    using (var bs = new System.IO.BufferedStream(fs, 17000000)) 
    { 
     for (var partIndex = 1; partIndex <= parts; partIndex++) 
     { 
      if (cancellationToken.IsCancellationRequested) 
       break; 

      // Todo: partIndex has to be prepended with one or more zeros to ensure correct sorting when downloading the object 
      var segmentUrl = url + "/" + partIndex; 

      using (var fsSub = new SomeStreamCopyClass(inputStream: bs, maximumToRead: segmentSize)) 
      using (var client = new HttpClient()) 
      { 
       client.Timeout = Timeout.InfiniteTimeSpan; 
       client.DefaultRequestHeaders.Add("X-Auth-Token", "SomeToken"); 

       var response = client.PutAsync(segmentUrl, new StreamContent(fsSub), cancellationToken).Result; 
       // Todo: Use response for verification 
      } 
     } 
    } 

    // TODO: Upload the manifest 
} 

だから私が実際に存在する何かをラインusing (var fsSub = new SomeStreamCopyClass(inputStream: bs, maximumToRead: segmentSize))を置き換えることができれば、私はそれが動作するはずだと思います。

答えて

1

あなたはこのような何かを試すことができます。

public class PartialFileStream : FileStream { 
    public PartialFileStream(string path, FileMode mode, FileAccess access, FileShare share, long firstChunkLength) 
     : base(path, mode, access, share) { 
     Advance(firstChunkLength); 
    } 

    public long ReadTillPosition { get; private set; } 

    private long _length; 
    public override long Length => _length; 

    public void Advance(long nextChunkLength) { 
     this.ReadTillPosition += nextChunkLength; 
     if (ReadTillPosition > base.Length) { 
      // if we are outside the stream, adjust 
      var diff = ReadTillPosition - base.Length; 
      nextChunkLength -= diff; 
      ReadTillPosition = base.Length; 
      if (nextChunkLength < 0) 
       nextChunkLength = 0; 
     } 
     _length = nextChunkLength; 
    } 

    public override int Read(byte[] array, int offset, int count) { 
     if (base.Position >= this.ReadTillPosition) 
      return 0; 

     if (base.Position + count > this.ReadTillPosition) 
      count = (int) (this.ReadTillPosition - base.Position); 


     return base.Read(array, offset, count); 
    } 
} 

は、基本的に我々はFileStreamから継承し、ReadTillPositionを指定までRead方法でのみお読みください。次のチャンクをアップロードするときは、次のチャンクの長さでReadTillPositionを進めてください。

+0

これを使用すると、 'client.PutAsync(segmentUrl、new StreamContent(bs)、cancellationToken).Result'という行はInnerExceptionsの束を持つExceptionをスローします:'要求の送信中にエラーが発生しました。要求は中止されました:要求はキャンセルされました。 - > 'すべてのバイトが書き込まれるまでストリームを閉じることはできません。'おそらく 'PutAsync'は' StreamContent(bs) '内のストリームを閉じますか? – natli

+0

このエラーは、Stream.Lengthを使用してContent-Length httpヘッダーを設定した後、_body_ストリームにデータを書き込もうとしていて、Stream.Lengthよりも少ないデータを読み取っていることがわかります。解決方法は、Stream.Lengthもオーバーライドし、現在のチャンクの長さに設定することです(私の更新された回答を参照)。 – Evk

+0

'Advance'メソッドの' if(ReadTillPosition> base.Length) 'に' ObjectDisposed'例外をスローします。私は本当に 'PutAsync'が' StreamContent'を処分し、 'StreamContent'が基底のストリームを処分すると考えています。だから私は、これが処分を防ぐ方法がない限り、これが継承で機能するとは思わない。 +1あなたの努力のために、これは異なるシナリオで動作するかもしれません – natli

関連する問題