csharp-sdk-upm/Storage/Source/Internal/File/Controller/QCloudCosFileController.cs

251 lines
9.4 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using LeanCloud.Storage.Internal;
namespace LeanCloud.Storage.Internal
{
internal class QCloudCosFileController : AVFileController
{
private object mutex = new object();
FileState fileState;
Stream data;
string bucket;
string token;
string uploadUrl;
bool done;
private long sliceSize = (long)CommonSize.KB512;
public QCloudCosFileController(IAVCommandRunner commandRunner) : base(commandRunner)
{
}
public Task<FileState> SaveAsync(FileState state,
Stream dataStream,
string sessionToken,
IProgress<AVUploadProgressEventArgs> progress,
CancellationToken cancellationToken)
{
if (state.Url != null)
{
return Task<FileState>.FromResult(state);
}
fileState = state;
data = dataStream;
return GetFileToken(fileState, cancellationToken).OnSuccess(_ =>
{
var fileToken = _.Result.Item2;
uploadUrl = fileToken["upload_url"].ToString();
token = fileToken["token"].ToString();
fileState.ObjectId = fileToken["objectId"].ToString();
bucket = fileToken["bucket"].ToString();
return FileSlice(cancellationToken).OnSuccess(t =>
{
if (done) return Task<FileState>.FromResult(state);
var response = t.Result.Item2;
var resumeData = response["data"] as IDictionary<string, object>;
if (resumeData.ContainsKey("access_url")) return Task<FileState>.FromResult(state);
var sliceSession = resumeData["session"].ToString();
var sliceOffset = long.Parse(resumeData["offset"].ToString());
return UploadSlice(sliceSession, sliceOffset, dataStream, progress, cancellationToken);
}).Unwrap();
}).Unwrap();
}
Task<FileState> UploadSlice(
string sessionId,
long offset,
Stream dataStream,
IProgress<AVUploadProgressEventArgs> progress,
CancellationToken cancellationToken)
{
long dataLength = dataStream.Length;
if (progress != null)
{
lock (mutex)
{
progress.Report(new AVUploadProgressEventArgs()
{
Progress = AVFileController.CalcProgress(offset, dataLength)
});
}
}
if (offset == dataLength)
{
return Task.FromResult<FileState>(fileState);
}
var sliceFile = GetNextBinary(offset, dataStream);
return ExcuteUpload(sessionId, offset, sliceFile, cancellationToken).OnSuccess(_ =>
{
offset += sliceFile.Length;
if (offset == dataLength)
{
done = true;
return Task.FromResult<FileState>(fileState);
}
var response = _.Result.Item2;
var resumeData = response["data"] as IDictionary<string, object>;
var sliceSession = resumeData["session"].ToString();
return UploadSlice(sliceSession, offset, dataStream, progress, cancellationToken);
}).Unwrap();
}
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> ExcuteUpload(string sessionId, long offset, byte[] sliceFile, CancellationToken cancellationToken)
{
var body = new Dictionary<string, object>();
body.Add("op", "upload_slice");
body.Add("session", sessionId);
body.Add("offset", offset.ToString());
return PostToQCloud(body, sliceFile, cancellationToken);
}
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> FileSlice(CancellationToken cancellationToken)
{
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
var body = new Dictionary<string, object>();
if (data.Length <= (long)CommonSize.KB512)
{
body.Add("op", "upload");
body.Add("sha", HexStringFromBytes(sha1.ComputeHash(data)));
var wholeFile = GetNextBinary(0, data);
return PostToQCloud(body, wholeFile, cancellationToken).OnSuccess(_ =>
{
if (_.Result.Item1 == HttpStatusCode.OK)
{
done = true;
}
return _.Result;
});
}
else
{
body.Add("op", "upload_slice");
body.Add("filesize", data.Length);
body.Add("sha", HexStringFromBytes(sha1.ComputeHash(data)));
body.Add("slice_size", (long)CommonSize.KB512);
}
return PostToQCloud(body, null, cancellationToken);
}
public static string HexStringFromBytes(byte[] bytes)
{
var sb = new StringBuilder();
foreach (byte b in bytes)
{
var hex = b.ToString("x2");
sb.Append(hex);
}
return sb.ToString();
}
public static string SHA1HashStringForUTF8String(string s)
{
byte[] bytes = Encoding.UTF8.GetBytes(s);
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
byte[] hashBytes = sha1.ComputeHash(bytes);
return HexStringFromBytes(hashBytes);
}
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> PostToQCloud(
Dictionary<string, object> body,
byte[] sliceFile,
CancellationToken cancellationToken)
{
IList<KeyValuePair<string, string>> sliceHeaders = new List<KeyValuePair<string, string>>();
sliceHeaders.Add(new KeyValuePair<string, string>("Authorization", this.token));
string contentType;
long contentLength;
var tempStream = HttpUploadFile(sliceFile, fileState.CloudName, out contentType, out contentLength, body);
sliceHeaders.Add(new KeyValuePair<string, string>("Content-Type", contentType));
var rtn = AVClient.RequestAsync(new Uri(this.uploadUrl), "POST", sliceHeaders, tempStream, null, cancellationToken).OnSuccess(_ =>
{
var dic = AVClient.ReponseResolve(_.Result, CancellationToken.None);
return dic;
});
return rtn;
}
public static Stream HttpUploadFile(byte[] file, string fileName, out string contentType, out long contentLength, IDictionary<string, object> nvc)
{
string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
byte[] boundarybytes = StringToAscii("\r\n--" + boundary + "\r\n");
contentType = "multipart/form-data; boundary=" + boundary;
MemoryStream rs = new MemoryStream();
string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
foreach (string key in nvc.Keys)
{
rs.Write(boundarybytes, 0, boundarybytes.Length);
string formitem = string.Format(formdataTemplate, key, nvc[key]);
byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(formitem);
rs.Write(formitembytes, 0, formitembytes.Length);
}
rs.Write(boundarybytes, 0, boundarybytes.Length);
if (file != null)
{
string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
string header = string.Format(headerTemplate, "fileContent", fileName, "application/octet-stream");
byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
rs.Write(headerbytes, 0, headerbytes.Length);
rs.Write(file, 0, file.Length);
}
byte[] trailer = StringToAscii("\r\n--" + boundary + "--\r\n");
rs.Write(trailer, 0, trailer.Length);
contentLength = rs.Length;
rs.Position = 0;
var tempBuffer = new byte[rs.Length];
rs.Read(tempBuffer, 0, tempBuffer.Length);
return new MemoryStream(tempBuffer);
}
public static byte[] StringToAscii(string s)
{
byte[] retval = new byte[s.Length];
for (int ix = 0; ix < s.Length; ++ix)
{
char ch = s[ix];
if (ch <= 0x7f) retval[ix] = (byte)ch;
else retval[ix] = (byte)'?';
}
return retval;
}
byte[] GetNextBinary(long completed, Stream dataStream)
{
if (completed + sliceSize > dataStream.Length)
{
sliceSize = dataStream.Length - completed;
}
byte[] chunkBinary = new byte[sliceSize];
dataStream.Seek(completed, SeekOrigin.Begin);
dataStream.Read(chunkBinary, 0, (int)sliceSize);
return chunkBinary;
}
}
}