2016-05-25 8 views
2

Ich arbeite an einer Methode zum Hochladen großer Dateien an einen OpenStack-Provider. OpenStack hat normalerweise eine Beschränkung von 5 GB pro Datei, aber dies kann umgangen werden, indem die Datei in Segmente hochgeladen und dann ein Manifest hinzugefügt wird.Übergeben eines Segments eines bereits geöffneten FileStream an einen Stream-Parameter

Nach http://docs.openstack.org/developer/swift/overview_large_objects.html geschieht dies wie folgt:

# 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 

Ich versuche, den ersten Teil in C# zu erstellen, aber ich möchte das Öffnen/Schließen/Wiedereröffnung des Filestream verhindern. Dies stellt ein Problem dar, weil ich HttpClient.PutAsync für das tatsächliche Hochladen der Datei verwende (und weiterhin verwenden möchte). Für eine Datei, die nicht segmentierten Upload benötigt, sieht das wie folgt aus:

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 }; 
    } 
} 

Also, um diese Funktion zu erhalten, ich brauche einen Strom HttpClient.PutAsync zu übergeben, um ein ein bereits geöffnet strom liest weiter Höchstbetrag.

Der Code, den ich habe, ist:

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 
} 

Also, wenn ich die Linie using (var fsSub = new SomeStreamCopyClass(inputStream: bs, maximumToRead: segmentSize)) mit etwas ersetzen könnte, die tatsächlich vorhanden ist, ich denke, es sollte funktionieren.

Antwort

1

Sie können so etwas wie dies versuchen:

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); 
    } 
} 

Grundsätzlich erben wir von FileStream und in Read Methode nur lesen, bis ReadTillPosition angegeben. Wenn Sie den nächsten Chunk hochladen, gehen Sie ReadTillPosition um die Länge des nächsten Chunks vor.

+0

Wenn ich dies verwende, löst die Zeile 'client.PutAsync (segmentUrl, neuer StreamContent (bs), cancellationToken) .Result 'eine Exception mit einer Reihe von InnerExceptions aus:' Beim Senden der Anfrage ist ein Fehler aufgetreten -> 'The Anfrage wurde abgebrochen: Die Anfrage wurde abgebrochen. '->' Stream kann nicht geschlossen werden, bis alle Bytes geschrieben sind '- vielleicht schließt 'PutAsync' den Stream innerhalb' StreamContent (bs) '- irgendwelche Ideen? – natli

+0

Dieser Fehler bedeutet, dass es Stream.Length verwendet, um den Content-Length-HTTP-Header festzulegen, dann versucht es, Daten in den Stream _body_ zu schreiben, und entdeckt, dass weniger Daten als Stream.Length gelesen werden. Lösung wäre, auch Stream.Length zu überschreiben und auf die Länge Ihres aktuellen Chunks zu setzen (siehe meine aktualisierte Antwort). – Evk

+0

Es wirft eine 'ObjectDisposed'-Ausnahme bei' if (ReadTillPosition> base.Length) 'innerhalb der' Advance'-Methode. Ich glaube wirklich, dass 'PutAsync' den' StreamContent' entsorgt, der wiederum den zugrunde liegenden Stream entsorgt. Ich denke also nicht, dass dies mit der Vererbung funktionieren wird, es sei denn, es gibt eine Möglichkeit, die Entsorgung zu verhindern. +1 für Ihre Mühe, könnte dies in verschiedenen Szenarien funktionieren – natli