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 SaveAsync(FileState state, Stream dataStream, string sessionToken, IProgress progress, CancellationToken cancellationToken) { if (state.Url != null) { return Task.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.FromResult(state); var response = t.Result.Item2; var resumeData = response["data"] as IDictionary; if (resumeData.ContainsKey("access_url")) return Task.FromResult(state); var sliceSession = resumeData["session"].ToString(); var sliceOffset = long.Parse(resumeData["offset"].ToString()); return UploadSlice(sliceSession, sliceOffset, dataStream, progress, cancellationToken); }).Unwrap(); }).Unwrap(); } Task UploadSlice( string sessionId, long offset, Stream dataStream, IProgress 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); } var sliceFile = GetNextBinary(offset, dataStream); return ExcuteUpload(sessionId, offset, sliceFile, cancellationToken).OnSuccess(_ => { offset += sliceFile.Length; if (offset == dataLength) { done = true; return Task.FromResult(fileState); } var response = _.Result.Item2; var resumeData = response["data"] as IDictionary; var sliceSession = resumeData["session"].ToString(); return UploadSlice(sliceSession, offset, dataStream, progress, cancellationToken); }).Unwrap(); } Task>> ExcuteUpload(string sessionId, long offset, byte[] sliceFile, CancellationToken cancellationToken) { var body = new Dictionary(); body.Add("op", "upload_slice"); body.Add("session", sessionId); body.Add("offset", offset.ToString()); return PostToQCloud(body, sliceFile, cancellationToken); } Task>> FileSlice(CancellationToken cancellationToken) { SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); var body = new Dictionary(); 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>> PostToQCloud( Dictionary body, byte[] sliceFile, CancellationToken cancellationToken) { IList> sliceHeaders = new List>(); sliceHeaders.Add(new KeyValuePair("Authorization", this.token)); string contentType; long contentLength; var tempStream = HttpUploadFile(sliceFile, fileState.CloudName, out contentType, out contentLength, body); sliceHeaders.Add(new KeyValuePair("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 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; } } }