diff --git a/.gitignore b/.gitignore index ced686fa..3c395f0e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,6 @@ obj/ [Rr]elease*/ _ReSharper*/ [Tt]est[Rr]esult* -/src/packages /packages /bin +/src/packages diff --git a/.travis.yml b/.travis.yml index 641e6286..e90780be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,11 @@ language: csharp install: - sudo apt-get install nunit - + - sudo apt-get install Newtonsoft.Json before_script: - export isTravisTest=true script: - - make build-and-test \ No newline at end of file + - make build-and-test diff --git a/LICENSE b/LICENSE index 829e5000..27e5fd6e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017 qiniu.com +Copyright (c) 2019 qiniu.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 7f08c751..04b52551 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,10 @@ build-and-test: cp tools/net40/Newtonsoft.Json.dll bin/ cp tools/net40/nunit.framework.dll bin/ cp tools/files/* bin/ - - - xbuild src/Qiniu.sln - - nunit-console bin/QiniuTests.dll +<<<<<<< HEAD + + msbuild src/Qiniu.sln +======= + msbuild src/Qiniu.sln +>>>>>>> f354e68f73bc7383528e9a5ea67f760e2f458492 + nunit-console bin/Qiniu.dll diff --git a/README.md b/README.md index 620601ba..565ce689 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## 使用 -* 参考文档:[七牛云存储 C# SDK 使用指南](https://developer.qiniu.com/kodo/sdk/1237/csharp) +* 参考文档:[七牛云存储 C# SDK 使用指南](https://developer.qiniu.com/kodo/sdk/4055/csharp-sdk) * 可以参考我们为大家精心准备的使用 [实例](https://github.com/qiniu/csharp-sdk/tree/master/src/QiniuTests) @@ -21,4 +21,4 @@ ## 许可证 -Copyright (c) 2017 [qiniu.com](www.qiniu.com) +Copyright (c) 2019 [qiniu.com](www.qiniu.com) \ No newline at end of file diff --git a/src/Qiniu.sln b/src/Qiniu.sln index 7db84cf9..6a544557 100644 --- a/src/Qiniu.sln +++ b/src/Qiniu.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2002 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qiniu", "Qiniu\Qiniu.csproj", "{2F5B0328-DE8B-4B53-A500-3077E340A51B}" EndProject @@ -10,40 +10,29 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|Any CPU.ActiveCfg = Debug|x86 - {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x64.ActiveCfg = Debug|x64 - {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x64.Build.0 = Debug|x64 + {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|Any CPU.Build.0 = Debug|Any CPU {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x86.ActiveCfg = Debug|x86 {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x86.Build.0 = Debug|x86 - {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|Any CPU.ActiveCfg = Release|x86 - {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x64.ActiveCfg = Release|x64 - {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x64.Build.0 = Release|x64 + {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|Any CPU.Build.0 = Release|Any CPU {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x86.ActiveCfg = Release|x86 {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x86.Build.0 = Release|x86 {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x64.ActiveCfg = Debug|x64 - {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x64.Build.0 = Debug|x64 {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x86.ActiveCfg = Debug|Any CPU {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x86.Build.0 = Debug|Any CPU {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|Any CPU.ActiveCfg = Release|Any CPU {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|Any CPU.Build.0 = Release|Any CPU - {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x64.ActiveCfg = Release|x64 - {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x64.Build.0 = Release|x64 {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x86.ActiveCfg = Release|Any CPU {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {0358A72B-AB04-47A5-8AC2-BCDE2447D2A4} - EndGlobalSection EndGlobal diff --git a/src/Qiniu/Qiniu.csproj b/src/Qiniu/Qiniu.csproj index 588c7313..47f998e3 100644 --- a/src/Qiniu/Qiniu.csproj +++ b/src/Qiniu/Qiniu.csproj @@ -1,171 +1,183 @@ - - - - - Release - AnyCPU - {2F5B0328-DE8B-4B53-A500-3077E340A51B} - Library - Properties - Qiniu - Qiniu - v4.0 - 512 - - - - false - - - QiniuCSharpSDK.snk - - - true - bin\x86\Debug\ - DEBUG;TRACE;Net40 - ..\..\bin\net40\Qiniu.xml - full - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\x86\Release\ - TRACE;Net40 - ..\..\bin\net40\Qiniu.xml - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - true - bin\x64\Debug\ - DEBUG;TRACE;Net40 - ..\..\bin\net40\Qiniu.xml - full - x64 - prompt - MinimumRecommendedRules.ruleset - - - bin\x64\Release\ - TRACE;Net40 - ..\..\bin\net40\Qiniu.xml - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\Newtonsoft.Json.10.0.3\lib\net40\Newtonsoft.Json.dll - - - ..\packages\NUnit.3.8.1\lib\net40\nunit.framework.dll - - - + + + + + Release + AnyCPU + {2F5B0328-DE8B-4B53-A500-3077E340A51B} + Library + Properties + Qiniu + Qiniu + v4.0 + 512 + + + + true + full + false + ..\..\bin\net40\obj\ + ..\..\bin\ + DEBUG;TRACE;Net40 + prompt + 4 + false + ..\..\bin\Qiniu.xml + + + pdbonly + true + ..\..\bin\net40\obj\ + ..\..\bin\ + TRACE;Net40 + prompt + 4 + false + ..\..\bin\Qiniu.xml + + + false + + + + + + + true + bin\x86\Debug\ + DEBUG;TRACE;Net40 + ..\..\bin\net40\Qiniu.xml + full + x86 + prompt + MinimumRecommendedRules.ruleset + false + + + bin\x86\Release\ + TRACE;Net40 + ..\..\bin\net40\Qiniu.xml + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + false + + + + + + + ..\packages\Newtonsoft.Json.11.0.2\lib\net40\Newtonsoft.Json.dll + True + + + ..\packages\NUnit.3.7.1\lib\net40\nunit.framework.dll + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --> \ No newline at end of file diff --git a/src/Qiniu/Storage/BucketManager.cs b/src/Qiniu/Storage/BucketManager.cs index 9565ee70..ecb9532a 100644 --- a/src/Qiniu/Storage/BucketManager.cs +++ b/src/Qiniu/Storage/BucketManager.cs @@ -3,6 +3,7 @@ using Qiniu.Http; using Qiniu.Util; using System.Collections.Generic; +using Newtonsoft.Json; namespace Qiniu.Storage { /// @@ -28,7 +29,6 @@ public BucketManager(Mac mac, Config config) this.config = config; } - /// /// 获取空间文件信息 /// @@ -342,6 +342,46 @@ public HttpResult ChangeType(string bucket, string key, int fileType) return result; } + /// + /// 修改文件状态 + /// + /// 空间名称 + /// 文件key + /// 修改后的文件状态,0表示普通存储,1表示低频存储 + /// 匹配条件 + /// 状态码为200时表示OK + public HttpResult ChangeStatus(string bucket, string key, int status,Dictionary cond) + { + HttpResult result = new HttpResult(); + + try + { + string chstatusUrl = string.Format("{0}{1}", this.config.RsHost(this.mac.AccessKey, bucket), + ChangeStatusOp(bucket, key, status,cond)); + string token = auth.CreateManageToken(chstatusUrl); + result = httpManager.Post(chstatusUrl, token); + } + catch (QiniuException ex) + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("[{0}] [chStatus] Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); + Exception e = ex; + while (e != null) + { + sb.Append(e.Message + " "); + e = e.InnerException; + } + sb.AppendLine(); + + result.Code = ex.HttpResult.Code; + result.RefCode = ex.HttpResult.Code; + result.Text = ex.HttpResult.Text; + result.RefText += sb.ToString(); + } + + return result; + } + /// /// 批处理 /// @@ -521,6 +561,133 @@ public DomainsResult Domains(string bucket) return result; } + /// + /// 创建空间 + /// + /// 空间名称 + /// 存储区域 + /// 状态码为200时表示OK + public HttpResult CreateBucket(string bucket, string region) + { + HttpResult result = new HttpResult(); + try + { + string scheme = this.config.UseHttps ? "https://" : "http://"; + string rsHost = string.Format("{0}{1}", scheme, Config.DefaultRsHost); + string mkbucketv2Url = string.Format("{0}{1}", rsHost, + CreateBucketOp(bucket, region)); + Console.WriteLine(mkbucketv2Url); + string token = auth.CreateManageToken(mkbucketv2Url); + result = httpManager.Post(mkbucketv2Url, token); + } + catch (QiniuException ex) + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("[{0}] [createBucket] Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); + Exception e = ex; + while (e != null) + { + sb.Append(e.Message + " "); + e = e.InnerException; + } + sb.AppendLine(); + + result.Code = ex.HttpResult.Code; + result.RefCode = ex.HttpResult.Code; + result.Text = ex.HttpResult.Text; + result.RefText += sb.ToString(); + } + + return result; + } + /// + /// + /// 获取空间文件列表 + /// listFilesV2(bucket, prefix, marker, limit, delimiter) + /// + /// bucket: 目标空间名称 + /// + /// prefix: 返回指定文件名前缀的文件列表(prefix可设为null) + /// + /// marker: 考虑到设置limit后返回的文件列表可能不全(需要重复执行listFiles操作) + /// 执行listFiles操作时使用marker标记来追加新的结果 + /// 特别注意首次执行listFiles操作时marker为null + /// + /// limit: 每次返回结果所包含的文件总数限制(limit最大值1000,建议值100) + /// + /// delimiter: 分隔符,比如-或者/等等,可以模拟作为目录结构(参考下述示例) + /// 假设指定空间中有2个文件 fakepath/1.txt fakepath/2.txt + /// 现设置分隔符delimiter = / 得到返回结果items =[],commonPrefixes = [fakepath/] + /// 然后调整prefix = fakepath/ delimiter = null 得到所需结果items = [1.txt,2.txt] + /// 于是可以在本地先创建一个目录fakepath,然后在该目录下写入items中的文件 + /// + /// + /// 空间名称 + /// 前缀 + /// 标记 + /// 数量限制 + /// 分隔符 + /// 文件列表获取结果 + public ListResultV2 ListFilesV2(string bucket, string prefix, string marker, int limit, string delimiter) + { + ListResultV2 result = new ListResultV2(); + try + { + StringBuilder sb = new StringBuilder("/v2/list?bucket=" + bucket); + + if (!string.IsNullOrEmpty(marker)) + { + sb.Append("&marker=" + marker); + } + + if (!string.IsNullOrEmpty(prefix)) + { + sb.Append("&prefix=" + prefix); + } + + if (!string.IsNullOrEmpty(delimiter)) + { + sb.Append("&delimiter=" + delimiter); + } + + if (limit > 1000 || limit < 1) + { + sb.Append("&limit=1000"); + } + else + { + sb.Append("&limit=" + limit); + } + + string listUrl = string.Format("{0}{1}", this.config.RsfHost(this.mac.AccessKey, bucket), sb.ToString()); + string token = auth.CreateManageToken(listUrl); + + + HttpResult hr = httpManager.Post(listUrl, token); + result.Shadow(hr); + + } + catch (QiniuException ex) + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("[{0}] [listFiles] Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); + Exception e = ex; + while (e != null) + { + sb.Append(e.Message + " "); + e = e.InnerException; + } + sb.AppendLine(); + + result.Code = ex.HttpResult.Code; + result.RefCode = ex.HttpResult.Code; + result.Text = ex.HttpResult.Text; + result.RefText += sb.ToString(); + } + + return result; + } + /// /// /// 获取空间文件列表 @@ -585,6 +752,7 @@ public ListResult ListFiles(string bucket, string prefix, string marker, int lim string token = auth.CreateManageToken(listUrl); HttpResult hr = httpManager.Post(listUrl, token); + Console.WriteLine(hr); result.Shadow(hr); } catch (QiniuException ex) @@ -753,6 +921,40 @@ public string ChangeTypeOp(string bucket, string key, int fileType) fileType); } + /// + /// 生成chstatus操作字符串 + /// + /// 空间名称 + /// 文件key + /// 修改后文件状态 + /// 匹配条件 + /// chstatus操作字符串 + public string ChangeStatusOp(string bucket, string key, int status,Dictionary cond) + { + if(cond==null||cond.Keys.Count==0) + { + return string.Format("/chstatus/{0}/status/{1}", Base64.UrlSafeBase64Encode(bucket, key), + status); + } + else + { + string condstr = Base64.UrlSafeBase64Encode(StringHelper.UrlFormEncode(cond)); + return string.Format("/chstatus/{0}/status/{1}/cond/{2}", Base64.UrlSafeBase64Encode(bucket, key), + status,condstr); + } + } + + /// + /// 生成mkbucketv2操作字符串 + /// + /// 空间名称 + /// 存储区域 + /// mkbucketv2操作字符串 + public string CreateBucketOp(string bucket, string region) + { + return string.Format("/mkbucketv2/{0}/region/{1}", Base64.UrlSafeBase64Encode(bucket), + region); + } /// /// 生成fetch操作字符串 /// diff --git a/src/Qiniu/Storage/Config.cs b/src/Qiniu/Storage/Config.cs index 839ec5f4..24a9d2d9 100644 --- a/src/Qiniu/Storage/Config.cs +++ b/src/Qiniu/Storage/Config.cs @@ -1,8 +1,7 @@ - namespace Qiniu.Storage { /// - /// 配置信息,主要包括Zone配置(另请参阅Zone模块) + /// 配置信息,主要包括Region配置(另请参阅Region模块) /// 目前已支持的机房包括: /// 华东(CN_East), 华北(CN_North), 华南(CN_South), 北美(US_North), 新加坡(AS_Singapore) /// 默认设置为华东机房(CN_East) @@ -20,7 +19,11 @@ public class Config /// /// 空间所在的区域(Zone) /// - public Zone Zone = null; + public Region Zone = null; + /// + /// 空间所在的区域(Region) + /// + public Region Region = null; /// /// 是否采用https域名 /// @@ -51,12 +54,20 @@ public class Config public string RsHost(string ak, string bucket) { string scheme = UseHttps ? "https://" : "http://"; - Zone z = this.Zone; - if (z == null) + Region r = null; + if (this.Zone!=null) + { + r = this.Zone; + }else { - z = ZoneHelper.QueryZone(ak, bucket); + r = this.Region; } - return string.Format("{0}{1}", scheme, z.RsHost); + + if (r == null) + { + r = RegionHelper.QueryRegion(ak, bucket); + } + return string.Format("{0}{1}", scheme, r.RsHost); } /// @@ -68,12 +79,20 @@ public string RsHost(string ak, string bucket) public string RsfHost(string ak, string bucket) { string scheme = UseHttps ? "https://" : "http://"; - Zone z = this.Zone; - if (z == null) + Region r = null; + if (this.Zone != null) { - z = ZoneHelper.QueryZone(ak, bucket); + r = this.Zone; } - return string.Format("{0}{1}", scheme, z.RsfHost); + else + { + r = this.Region; + } + if (r == null) + { + r = RegionHelper.QueryRegion(ak, bucket); + } + return string.Format("{0}{1}", scheme, r.RsfHost); } /// @@ -85,12 +104,20 @@ public string RsfHost(string ak, string bucket) public string ApiHost(string ak, string bucket) { string scheme = UseHttps ? "https://" : "http://"; - Zone z = this.Zone; - if (z == null) + Region r = null; + if (this.Zone != null) + { + r = this.Zone; + } + else { - z = ZoneHelper.QueryZone(ak, bucket); + r = this.Region; } - return string.Format("{0}{1}", scheme, z.ApiHost); + if (r == null) + { + r = RegionHelper.QueryRegion(ak, bucket); + } + return string.Format("{0}{1}", scheme, r.ApiHost); } /// @@ -102,12 +129,20 @@ public string ApiHost(string ak, string bucket) public string IovipHost(string ak, string bucket) { string scheme = UseHttps ? "https://" : "http://"; - Zone z = this.Zone; - if (z == null) + Region r = null; + if (this.Zone != null) + { + r = this.Zone; + } + else + { + r = this.Region; + } + if (r == null) { - z = ZoneHelper.QueryZone(ak, bucket); + r = RegionHelper.QueryRegion(ak, bucket); } - return string.Format("{0}{1}", scheme, z.IovipHost); + return string.Format("{0}{1}", scheme, r.IovipHost); } /// @@ -119,18 +154,26 @@ public string IovipHost(string ak, string bucket) public string UpHost(string ak, string bucket) { string scheme = UseHttps ? "https://" : "http://"; - Zone z = this.Zone; - if (z == null) + Region r = null; + if (this.Zone != null) + { + r = this.Zone; + } + else + { + r = this.Region; + } + if (r == null) { - z = ZoneHelper.QueryZone(ak, bucket); + r = RegionHelper.QueryRegion(ak, bucket); } - string upHost = z.SrcUpHosts[0]; + string upHost = r.SrcUpHosts[0]; if (this.UseCdnDomains) { - upHost = z.CdnUpHosts[0]; + upHost = r.CdnUpHosts[0]; } return string.Format("{0}{1}", scheme, upHost); } } -} +} \ No newline at end of file diff --git a/src/Qiniu/Storage/FileInfo.cs b/src/Qiniu/Storage/FileInfo.cs index 3ab21eec..29059b08 100644 --- a/src/Qiniu/Storage/FileInfo.cs +++ b/src/Qiniu/Storage/FileInfo.cs @@ -35,6 +35,5 @@ public class FileInfo /// [JsonProperty("type")] public int FileType { get; set; } - } } diff --git a/src/Qiniu/Storage/FormUploader.cs b/src/Qiniu/Storage/FormUploader.cs index abe65606..ce5f5ec6 100644 --- a/src/Qiniu/Storage/FormUploader.cs +++ b/src/Qiniu/Storage/FormUploader.cs @@ -74,7 +74,7 @@ public HttpResult UploadData(byte[] data, string key, string token, PutExtra ext /// (确定长度的)数据流 /// 要保存的key /// 上传凭证 - /// 上传可选设置 + /// 上传可选设置 /// 上传数据流后的返回结果 public HttpResult UploadStream(Stream stream, string key, string token, PutExtra putExtra) { @@ -101,139 +101,148 @@ public HttpResult UploadStream(Stream stream, string key, string token, PutExtra HttpResult result = new HttpResult(); - using (stream) + try { - try - { - string boundary = HttpManager.CreateFormDataBoundary(); - StringBuilder bodyBuilder = new StringBuilder(); - bodyBuilder.AppendLine("--" + boundary); - - if (key != null) - { - //write key when it is not null - bodyBuilder.AppendLine("Content-Disposition: form-data; name=\"key\""); - bodyBuilder.AppendLine(); - bodyBuilder.AppendLine(key); - bodyBuilder.AppendLine("--" + boundary); - } + string boundary = HttpManager.CreateFormDataBoundary(); + StringBuilder bodyBuilder = new StringBuilder(); + bodyBuilder.AppendLine("--" + boundary); - //write token - bodyBuilder.AppendLine("Content-Disposition: form-data; name=\"token\""); + if (key != null) + { + //write key when it is not null + bodyBuilder.AppendLine("Content-Disposition: form-data; name=\"key\""); bodyBuilder.AppendLine(); - bodyBuilder.AppendLine(token); + bodyBuilder.AppendLine(key); bodyBuilder.AppendLine("--" + boundary); + } - //write extra params - if (putExtra.Params != null && putExtra.Params.Count > 0) + //write token + bodyBuilder.AppendLine("Content-Disposition: form-data; name=\"token\""); + bodyBuilder.AppendLine(); + bodyBuilder.AppendLine(token); + bodyBuilder.AppendLine("--" + boundary); + + //write extra params + if (putExtra.Params != null && putExtra.Params.Count > 0) + { + foreach (var p in putExtra.Params) { - foreach (var p in putExtra.Params) + if (p.Key.StartsWith("x:")) { - if (p.Key.StartsWith("x:")) - { - bodyBuilder.AppendFormat("Content-Disposition: form-data; name=\"{0}\"", p.Key); - bodyBuilder.AppendLine(); - bodyBuilder.AppendLine(); - bodyBuilder.AppendLine(p.Value); - bodyBuilder.AppendLine("--" + boundary); - } + bodyBuilder.AppendFormat("Content-Disposition: form-data; name=\"{0}\"", p.Key); + bodyBuilder.AppendLine(); + bodyBuilder.AppendLine(); + bodyBuilder.AppendLine(p.Value); + bodyBuilder.AppendLine("--" + boundary); } } + } - //prepare data buffer - int bufferSize = 1024 * 1024; - byte[] buffer = new byte[bufferSize]; - int bytesRead = 0; - putExtra.ProgressHandler(0, stream.Length); - MemoryStream dataMS = new MemoryStream(); - while ((bytesRead = stream.Read(buffer, 0, bufferSize)) != 0) - { - dataMS.Write(buffer, 0, bytesRead); - } - - //write crc32 - uint crc32 = CRC32.CheckSumBytes(dataMS.ToArray()); - //write key when it is not null - bodyBuilder.AppendLine("Content-Disposition: form-data; name=\"crc32\""); - bodyBuilder.AppendLine(); - bodyBuilder.AppendLine(crc32.ToString()); - bodyBuilder.AppendLine("--" + boundary); + //prepare data buffer + int bufferSize = 1024 * 1024; + byte[] buffer = new byte[bufferSize]; + int bytesRead = 0; + putExtra.ProgressHandler(0, stream.Length); + MemoryStream dataMS = new MemoryStream(); + while ((bytesRead = stream.Read(buffer, 0, bufferSize)) != 0) + { + dataMS.Write(buffer, 0, bytesRead); + } - //write fname - bodyBuilder.AppendFormat("Content-Disposition: form-data; name=\"file\"; filename=\"{0}\"", fname); - bodyBuilder.AppendLine(); + //write crc32 + uint crc32 = CRC32.CheckSumBytes(dataMS.ToArray()); + //write key when it is not null + bodyBuilder.AppendLine("Content-Disposition: form-data; name=\"crc32\""); + bodyBuilder.AppendLine(); + bodyBuilder.AppendLine(crc32.ToString()); + bodyBuilder.AppendLine("--" + boundary); - //write mime type - bodyBuilder.AppendFormat("Content-Type: {0}", putExtra.MimeType); - bodyBuilder.AppendLine(); - bodyBuilder.AppendLine(); + //write fname + bodyBuilder.AppendFormat("Content-Disposition: form-data; name=\"file\"; filename=\"{0}\"", fname); + bodyBuilder.AppendLine(); - //write file data - StringBuilder bodyEnd = new StringBuilder(); - bodyEnd.AppendLine(); - bodyEnd.AppendLine("--" + boundary + "--"); + //write mime type + bodyBuilder.AppendFormat("Content-Type: {0}", putExtra.MimeType); + bodyBuilder.AppendLine(); + bodyBuilder.AppendLine(); - byte[] partData1 = Encoding.UTF8.GetBytes(bodyBuilder.ToString()); - byte[] partData2 = dataMS.ToArray(); - byte[] partData3 = Encoding.UTF8.GetBytes(bodyEnd.ToString()); + //write file data + StringBuilder bodyEnd = new StringBuilder(); + bodyEnd.AppendLine(); + bodyEnd.AppendLine("--" + boundary + "--"); - MemoryStream ms = new MemoryStream(); - ms.Write(partData1, 0, partData1.Length); - ms.Write(partData2, 0, partData2.Length); - ms.Write(partData3, 0, partData3.Length); + byte[] partData1 = Encoding.UTF8.GetBytes(bodyBuilder.ToString()); + byte[] partData2 = dataMS.ToArray(); + byte[] partData3 = Encoding.UTF8.GetBytes(bodyEnd.ToString()); - //get upload host - string ak = UpToken.GetAccessKeyFromUpToken(token); - string bucket = UpToken.GetBucketFromUpToken(token); - if (ak == null || bucket == null) - { - return HttpResult.InvalidToken; - } + MemoryStream ms = new MemoryStream(); + ms.Write(partData1, 0, partData1.Length); + ms.Write(partData2, 0, partData2.Length); + ms.Write(partData3, 0, partData3.Length); - string uploadHost = this.config.UpHost(ak, bucket); - putExtra.ProgressHandler(stream.Length / 5, stream.Length); - result = httpManager.PostMultipart(uploadHost, ms.ToArray(), boundary, null); - putExtra.ProgressHandler(stream.Length, stream.Length); - if (result.Code == (int)HttpCode.OK) - { - result.RefText += string.Format("[{0}] [FormUpload] Uploaded: #STREAM# ==> \"{1}\"\n", - DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), key); - } - else - { - result.RefText += string.Format("[{0}] [FormUpload] Failed: code = {1}, text = {2}\n", - DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), result.Code, result.Text); - } + //get upload host + string ak = UpToken.GetAccessKeyFromUpToken(token); + string bucket = UpToken.GetBucketFromUpToken(token); + if (ak == null || bucket == null) + { + return HttpResult.InvalidToken; + } - //close memory stream - ms.Close(); - dataMS.Close(); + string uploadHost = this.config.UpHost(ak, bucket); + putExtra.ProgressHandler(stream.Length / 5, stream.Length); + result = httpManager.PostMultipart(uploadHost, ms.ToArray(), boundary, null); + putExtra.ProgressHandler(stream.Length, stream.Length); + if (result.Code == (int)HttpCode.OK) + { + result.RefText += string.Format("[{0}] [FormUpload] Uploaded: #STREAM# ==> \"{1}\"\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), key); } - catch (Exception ex) + else { - StringBuilder sb = new StringBuilder(); - sb.AppendFormat("[{0}] [FormUpload] Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); - Exception e = ex; - while (e != null) - { - sb.Append(e.Message + " "); - e = e.InnerException; - } - sb.AppendLine(); + result.RefText += string.Format("[{0}] [FormUpload] Failed: code = {1}, text = {2}\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), result.Code, result.Text); + } - if (ex is QiniuException) - { - QiniuException qex = (QiniuException)ex; - result.Code = qex.HttpResult.Code; - result.RefCode = qex.HttpResult.Code; - result.Text = qex.HttpResult.Text; - result.RefText += sb.ToString(); - } - else + //close memory stream + ms.Close(); + dataMS.Close(); + } + catch (Exception ex) + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("[{0}] [FormUpload] Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); + Exception e = ex; + while (e != null) + { + sb.Append(e.Message + " "); + e = e.InnerException; + } + sb.AppendLine(); + + if (ex is QiniuException) + { + QiniuException qex = (QiniuException)ex; + result.Code = qex.HttpResult.Code; + result.RefCode = qex.HttpResult.Code; + result.Text = qex.HttpResult.Text; + result.RefText += sb.ToString(); + } + else + { + result.RefCode = (int)HttpCode.USER_UNDEF; + result.RefText += sb.ToString(); + } + } + finally + { + if (stream != null) + { + try { - result.RefCode = (int)HttpCode.USER_UNDEF; - result.RefText += sb.ToString(); + stream.Close(); + stream.Dispose(); } + catch (Exception) { } } } diff --git a/src/Qiniu/Storage/ListInfoV2.cs b/src/Qiniu/Storage/ListInfoV2.cs new file mode 100644 index 00000000..e516bfe8 --- /dev/null +++ b/src/Qiniu/Storage/ListInfoV2.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +namespace Qiniu.Storage +{ + /// + /// 获取空间文件(list操作) + /// + /// 返回JSON字符串 + /// + /// { + /// "marker":"MARKER", + /// "items": + /// [ + /// { + /// "key":"KEY", + /// "hash":"HASH", + /// "fsize":FSIZE, + /// "mimeType":"MIME_TYPE", + /// "putTime":PUT_TIME, + /// "type":FILE_TYPE + /// }, + /// { + /// ... + /// } + /// ], + /// "CmmonPrefixes":"COMMON_PREFIXES" + /// } + /// + /// + public class ListInfoV2 + { + + + /// + /// 文件列表 + /// + [JsonProperty("item", NullValueHandling = NullValueHandling.Ignore)] + public ListItemV2 Item { get; set; } + + /// + /// marker标记 + /// + [JsonProperty("marker", NullValueHandling = NullValueHandling.Ignore)] + public string Marker { get; set; } + + /// + /// dir + /// + [JsonProperty("dir", NullValueHandling = NullValueHandling.Ignore)] + public string Dir { get; set; } + } +} + diff --git a/src/Qiniu/Storage/ListItem.cs b/src/Qiniu/Storage/ListItem.cs index 28e1346d..b0838d28 100644 --- a/src/Qiniu/Storage/ListItem.cs +++ b/src/Qiniu/Storage/ListItem.cs @@ -43,6 +43,7 @@ public class ListItem [JsonProperty("type")] public int FileType { get; set; } + /// /// EndUser字段 /// diff --git a/src/Qiniu/Storage/ListItemV2.cs b/src/Qiniu/Storage/ListItemV2.cs new file mode 100644 index 00000000..b5a7bbc7 --- /dev/null +++ b/src/Qiniu/Storage/ListItemV2.cs @@ -0,0 +1,53 @@ +using Newtonsoft.Json; +namespace Qiniu.Storage +{ + /// + /// 文件描述(stat操作返回消息中包含的有效内容) + /// 与StatInfo一致 + /// + public class ListItemV2 + { + /// + /// 文件名 + /// + [JsonProperty("key")] + public string Key { get; set; } + + /// + /// 文件hash(ETAG) + /// + [JsonProperty("hash")] + public string Hash { get; set; } + + /// + /// 文件大小(字节) + /// + [JsonProperty("fsize")] + public long Fsize { get; set; } + + /// + /// 文件MIME类型 + /// + [JsonProperty("mimeType")] + public string MimeType { get; set; } + + /// + /// 上传时间 + /// + [JsonProperty("putTime")] + public long PutTime { get; set; } + + /// + /// 文件存储类型 + /// + [JsonProperty("type")] + public int FileType { get; set; } + + /// + /// status + /// + [JsonProperty("status")] + public int Status { get; set; } + } +} + diff --git a/src/Qiniu/Storage/ListResultV2.cs b/src/Qiniu/Storage/ListResultV2.cs new file mode 100644 index 00000000..8b23db09 --- /dev/null +++ b/src/Qiniu/Storage/ListResultV2.cs @@ -0,0 +1,89 @@ +using System.Text; +using Newtonsoft.Json; +using Qiniu.Http; +using System; +using System.Text.RegularExpressions; + +namespace Qiniu.Storage +{ + /// + /// 获取空间文件列表(list操作)的返回消息 + /// + public class ListResultV2 : HttpResult + { + /// + /// 文件列表信息 + /// + public ListInfoV2[] Result + { + get + { + string[] mm=null; + ListInfoV2[] info =null; + if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) + { + mm = Regex.Split(Text, "\\s+", RegexOptions.IgnoreCase); + info = new ListInfoV2[mm.Length-1]; + for (int i = 0; i < mm.Length- 1; i++) + { + info[i] = JsonConvert.DeserializeObject(mm[i]); + } + } + return info; + } + } + + /// + /// 转换为易读字符串格式 + /// + /// 便于打印和阅读的字符串> + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendFormat("code: {0}\n", Code); + + if (Result != null) + { + if (Result.Length != 0) + { + sb.AppendLine("items:"); + int i = 0, n = Result.Length; + foreach (var item in Result) + { + sb.AppendFormat("#{0}/{1}:Key={2}, Size={3}, Mime={4}, Hash={5}, Time={6}, Type={7}, Status={8}\n", + ++i, n, item.Item.Key, item.Item.Fsize, item.Item.MimeType, item.Item.Hash, item.Item.PutTime, item.Item.FileType,item.Item.Status); + } + } + } + else + { + if (!string.IsNullOrEmpty(Text)) + { + sb.AppendLine("text:"); + sb.AppendLine(Text); + } + } + sb.AppendLine(); + + sb.AppendFormat("ref-code: {0}\n", RefCode); + + if (!string.IsNullOrEmpty(RefText)) + { + sb.AppendLine("ref-text:"); + sb.AppendLine(RefText); + } + + if (RefInfo != null) + { + sb.AppendFormat("ref-info:\n"); + foreach (var d in RefInfo) + { + sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value)); + } + } + + return sb.ToString(); + } + } +} diff --git a/src/Qiniu/Storage/PutExtra.cs b/src/Qiniu/Storage/PutExtra.cs index 42673e0b..77578cfe 100644 --- a/src/Qiniu/Storage/PutExtra.cs +++ b/src/Qiniu/Storage/PutExtra.cs @@ -32,10 +32,5 @@ public class PutExtra /// 最大重试次数 /// public int MaxRetryTimes { set; get; } - - /// - /// 块并发上传的线程数量 - /// - public int BlockUploadThreads { set; get; } } } diff --git a/src/Qiniu/Storage/Region.cs b/src/Qiniu/Storage/Region.cs new file mode 100644 index 00000000..a0143ff3 --- /dev/null +++ b/src/Qiniu/Storage/Region.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Qiniu.Storage +{ + /// + /// 目前已支持的区域:华东/华北/华南/北美 + /// + public class Region + { + /// + /// 资源管理 + /// + public string RsHost { set; get; } + + /// + /// 资源列表 + /// + public string RsfHost { set; get; } + + /// + /// 数据处理 + /// + public string ApiHost { set; get; } + + /// + /// 镜像刷新、资源抓取 + /// + public string IovipHost { set; get; } + + /// + /// 资源上传 + /// + public string[] SrcUpHosts { set; get; } + + /// + /// CDN加速 + /// + public string[] CdnUpHosts { set; get; } + + /// + /// 华东 + /// + + public static Region Region_CN_East = new Region() + { + RsHost = "rs.qiniu.com", + RsfHost = "rsf.qiniu.com", + ApiHost = "api.qiniu.com", + IovipHost = "iovip.qbox.me", + SrcUpHosts = new string[] { "up.qiniup.com", + "up-nb.qiniup.com", "up-xs.qiniup.com" }, + CdnUpHosts = new string[] { "upload.qiniup.com", + "upload-nb.qiniup.com", "upload-xs.qiniup.com" } + }; + + /// + /// 华北 + /// + public static Region Region_CN_North = new Region() + { + RsHost = "rs-z1.qiniu.com", + RsfHost = "rsf-z1.qiniu.com", + ApiHost = "api-z1.qiniu.com", + IovipHost = "iovip-z1.qbox.me", + SrcUpHosts = new string[] { "up-z1.qiniup.com" }, + CdnUpHosts = new string[] { "upload-z1.qiniup.com" } + }; + + /// + /// 华南 + /// + public static Region Region_CN_South = new Region() + { + RsHost = "rs-z2.qiniu.com", + RsfHost = "rsf-z2.qiniu.com", + ApiHost = "api-z2.qiniu.com", + IovipHost = "iovip-z2.qbox.me", + SrcUpHosts = new string[] { "up-z2.qiniup.com", + "up-gz.qiniup.com", "up-fs.qiniup.com" }, + CdnUpHosts = new string[] { "upload-z2.qiniup.com", + "upload-gz.qiniup.com", "upload-fs.qiniup.com" } + }; + + /// + /// 北美 + /// + public static Region Region_US_North = new Region() + { + RsHost = "rs-na0.qiniu.com", + RsfHost = "rsf-na0.qiniu.com", + ApiHost = "api-na0.qiniu.com", + IovipHost = "iovip-na0.qbox.me", + SrcUpHosts = new string[] { "up-na0.qiniup.com" }, + CdnUpHosts = new string[] { "upload-na0.qiniup.com" } + }; + } +} diff --git a/src/Qiniu/Storage/RegionHelper.cs b/src/Qiniu/Storage/RegionHelper.cs new file mode 100644 index 00000000..4bebe261 --- /dev/null +++ b/src/Qiniu/Storage/RegionHelper.cs @@ -0,0 +1,116 @@ +using System; +using System.Text; +using System.Collections.Generic; +using Qiniu.Http; +using Newtonsoft.Json; + +namespace Qiniu.Storage +{ + /// + /// Zone辅助类,查询及配置Zone + /// + public class RegionHelper + { + private static Dictionary RegionCache = new Dictionary(); + private static object rwLock = new object(); + + /// + /// 从api.qiniu.com查询得到回复后,解析出upHost,然后根据upHost确定Zone + /// + /// AccessKek + /// 空间名称 + public static Region QueryRegion(string accessKey, string bucket) + { + Region region = null; + + string cacheKey = string.Format("{0}:{1}", accessKey, bucket); + + //check from cache + lock (rwLock) + { + if (RegionCache.ContainsKey(cacheKey)) + { + region = RegionCache[cacheKey]; + } + } + + if (region != null) + { + return region; + } + + //query from api + HttpResult hr = null; + try + { + string queryUrl = string.Format("https://api.qiniu.com/v2/query?ak={0}&bucket={1}", accessKey, bucket); + HttpManager httpManager = new HttpManager(); + hr = httpManager.Get(queryUrl, null); + if (hr.Code == (int)HttpCode.OK) + { + RegionInfo rInfo = JsonConvert.DeserializeObject(hr.Text); + if (rInfo != null) + { + region = new Region(); + region.SrcUpHosts = rInfo.Up.Src.Main; + region.CdnUpHosts = rInfo.Up.Acc.Main; + region.IovipHost = rInfo.Io.Src.Main[0]; + if (region.IovipHost.Contains("z1")) + { + region.ApiHost = "api-z1.qiniu.com"; + region.RsHost = "rs-z1.qiniu.com"; + region.RsfHost = "rsf-z1.qiniu.com"; + } + else if (region.IovipHost.Contains("z2")) + { + region.ApiHost = "api-z2.qiniu.com"; + region.RsHost = "rs-z2.qiniu.com"; + region.RsfHost = "rsf-z2.qiniu.com"; + } + else if (region.IovipHost.Contains("na0")) + { + region.ApiHost = "api-na0.qiniu.com"; + region.RsHost = "rs-na0.qiniu.com"; + region.RsfHost = "rsf-na0.qiniu.com"; + } + else + { + region.ApiHost = "api.qiniu.com"; + region.RsHost = "rs.qiniu.com"; + region.RsfHost = "rsf.qiniu.com"; + } + + lock (rwLock) + { + RegionCache[cacheKey] = region; + } + } + else + { + throw new Exception("JSON Deserialize failed: " + hr.Text); + } + } + else + { + throw new Exception("code: " + hr.Code + ", text: " + hr.Text + ", ref-text:" + hr.RefText); + } + } + catch (Exception ex) + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("[{0}] QueryRegion Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); + Exception e = ex; + while (e != null) + { + sb.Append(e.Message + " "); + e = e.InnerException; + } + sb.AppendLine(); + + throw new QiniuException(hr, sb.ToString()); + } + + return region; + } + } +} diff --git a/src/Qiniu/Storage/ZoneInfo.cs b/src/Qiniu/Storage/RegionInfo.cs similarity index 91% rename from src/Qiniu/Storage/ZoneInfo.cs rename to src/Qiniu/Storage/RegionInfo.cs index 395acad0..845715a7 100644 --- a/src/Qiniu/Storage/ZoneInfo.cs +++ b/src/Qiniu/Storage/RegionInfo.cs @@ -1,25 +1,23 @@ -namespace Qiniu.Storage +using System; +namespace Qiniu.Storage { /// /// 从uc.qbox.me返回的消息 /// - internal class ZoneInfo + internal class RegionInfo { public int Ttl { get; set; } public Io Io { set; get; } public Up Up { set; get; } } - internal class Io { public Src Src { set; get; } } - internal class Src { public string[] Main { set; get; } } - internal class Up { public UpDomain Acc { set; get; } diff --git a/src/Qiniu/Storage/ResumableUploader.cs b/src/Qiniu/Storage/ResumableUploader.cs index d98ca693..75912da5 100644 --- a/src/Qiniu/Storage/ResumableUploader.cs +++ b/src/Qiniu/Storage/ResumableUploader.cs @@ -22,7 +22,9 @@ public class ResumableUploader { private Config config; //分片上传块的大小,固定为4M,不可修改 - private const int BLOCK_SIZE = 4 * 1024 * 1024; + private const long BLOCK_SIZE = 4 * 1024 * 1024; + private const int DEFAULT_MAX_RETRY_TIMES = 3; + private long CHUNK_SIZE; // HTTP请求管理器(GET/POST等) private HttpManager httpManager; @@ -36,12 +38,12 @@ public ResumableUploader(Config config) if (config == null) { this.config = new Config(); - } - else + }else { this.config = config; } this.httpManager = new HttpManager(); + this.CHUNK_SIZE = ResumeChunk.GetChunkSize(this.config.ChunkSize); } @@ -68,7 +70,7 @@ public HttpResult UploadFile(string localFile, string key, string token, PutExtr } } - + /// /// 分片上传/断点续上传,带有自定义进度处理和上传控制,检查CRC32,可自动重试 @@ -95,282 +97,408 @@ public HttpResult UploadStream(Stream stream, string key, string upToken, PutExt { putExtra.UploadController = DefaultUploadController; } - - if (!(putExtra.BlockUploadThreads > 0 && putExtra.BlockUploadThreads <= 64)) + if (putExtra.MaxRetryTimes == 0) { - putExtra.BlockUploadThreads = 1; + putExtra.MaxRetryTimes = DEFAULT_MAX_RETRY_TIMES; } - using (stream) + //start to upload + try { - //start to upload - try + long fileSize = stream.Length; + long chunkSize = CHUNK_SIZE; + long blockSize = BLOCK_SIZE; + byte[] chunkBuffer = new byte[chunkSize]; + int blockCount = (int)((fileSize + blockSize - 1) / blockSize); + int index = 0; // zero block + + //check resume record file + ResumeInfo resumeInfo = null; + if (File.Exists(putExtra.ResumeRecordFile)) { - long uploadedBytes = 0; - long fileSize = stream.Length; - long blockCount = (fileSize + BLOCK_SIZE - 1) / BLOCK_SIZE; - - //check resume record file - ResumeInfo resumeInfo = null; - if (File.Exists(putExtra.ResumeRecordFile)) + bool useLastRecord = false; + resumeInfo = ResumeHelper.Load(putExtra.ResumeRecordFile); + if (resumeInfo != null && fileSize==resumeInfo.FileSize) { - resumeInfo = ResumeHelper.Load(putExtra.ResumeRecordFile); - if (resumeInfo != null && fileSize == resumeInfo.FileSize) + //check whether ctx expired + if (!UnixTimestamp.IsContextExpired(resumeInfo.ExpiredAt)) { - //check whether ctx expired - if (UnixTimestamp.IsContextExpired(resumeInfo.ExpiredAt)) - { - resumeInfo = null; - } + useLastRecord = true; } } - if (resumeInfo == null) + + if (useLastRecord) { - resumeInfo = new ResumeInfo() - { - FileSize = fileSize, - BlockCount = blockCount, - Contexts = new string[blockCount], - ExpiredAt = 0, - }; + index = resumeInfo.BlockIndex; } + } + if (resumeInfo == null) + { + resumeInfo = new ResumeInfo() + { + FileSize = fileSize, + BlockIndex = 0, + BlockCount = blockCount, + Contexts = new string[blockCount], + ExpiredAt = 0, + }; + } + + //read from offset + long offset = index * blockSize; + string context = null; + long expiredAt = 0; + long leftBytes = fileSize - offset; + long blockLeft = 0; + long blockOffset = 0; + HttpResult hr = null; + ResumeContext rc = null; + + stream.Seek(offset, SeekOrigin.Begin); + + var upts = UploadControllerAction.Activated; + bool bres = true; + var manualResetEvent = new ManualResetEvent(true); + int iTry = 0; + + while (leftBytes > 0) + { + // 每上传一个BLOCK之前,都要检查一下UPTS + upts = putExtra.UploadController(); - //calc upload progress - for (long blockIndex = 0; blockIndex < blockCount; blockIndex++) + if (upts == UploadControllerAction.Aborted) { - string context = resumeInfo.Contexts[blockIndex]; - if (!string.IsNullOrEmpty(context)) + result.Code = (int)HttpCode.USER_CANCELED; + result.RefCode = (int)HttpCode.USER_CANCELED; + result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is aborted\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); + + return result; + } + else if (upts == UploadControllerAction.Suspended) + { + if (bres) { - uploadedBytes += BLOCK_SIZE; + bres = false; + manualResetEvent.Reset(); + + result.RefCode = (int)HttpCode.USER_PAUSED; + result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is paused\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); } + manualResetEvent.WaitOne(1000); } - - //set upload progress - putExtra.ProgressHandler(uploadedBytes, fileSize); - - //init block upload error - //check not finished blocks to upload - UploadControllerAction upCtrl = putExtra.UploadController(); - ManualResetEvent manualResetEvent = new ManualResetEvent(false); - Dictionary blockDataDict = new Dictionary(); - Dictionary blockMakeResults = new Dictionary(); - Dictionary uploadedBytesDict = new Dictionary(); - uploadedBytesDict.Add("UploadProgress", uploadedBytes); - byte[] blockBuffer = new byte[BLOCK_SIZE]; - for (long blockIndex = 0; blockIndex < blockCount; blockIndex++) + else { - string context = resumeInfo.Contexts[blockIndex]; - if (string.IsNullOrEmpty(context)) + if (!bres) + { + bres = true; + manualResetEvent.Set(); + + result.RefCode = (int)HttpCode.USER_RESUMED; + result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is resumed\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); + } + + #region one-block + + #region mkblk + if (leftBytes < BLOCK_SIZE) + { + blockSize = leftBytes; + } + else + { + blockSize = BLOCK_SIZE; + } + + if (leftBytes < CHUNK_SIZE) + { + chunkSize = leftBytes; + } + else + { + chunkSize = CHUNK_SIZE; + } + + //read data buffer + stream.Read(chunkBuffer, 0, (int)chunkSize); + + iTry = 0; + while (++iTry <= putExtra.MaxRetryTimes) { - //check upload controller action before each chunk - while (true) + result.RefText += string.Format("[{0}] [ResumableUpload] try mkblk#{1}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), iTry); + + hr = MakeBlock(chunkBuffer, blockSize, chunkSize, upToken); + if (hr.Code == (int)HttpCode.OK && hr.RefCode != (int)HttpCode.USER_NEED_RETRY) { - upCtrl = putExtra.UploadController(); + break; + } + } + + if (hr.Code != (int)HttpCode.OK || hr.RefCode == (int)HttpCode.USER_NEED_RETRY) + { + result.Shadow(hr); + result.RefText += string.Format("[{0}] [ResumableUpload] Error: mkblk: code = {1}, text = {2}, offset = {3}, blockSize = {4}, chunkSize = {5}\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Code, hr.Text, offset, blockSize, chunkSize); - if (upCtrl == UploadControllerAction.Aborted) - { - result.Code = (int)HttpCode.USER_CANCELED; - result.RefCode = (int)HttpCode.USER_CANCELED; - result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is aborted\n", - DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); - manualResetEvent.Set(); - return result; - } - else if (upCtrl == UploadControllerAction.Suspended) + return result; + } + + if ((rc = JsonConvert.DeserializeObject(hr.Text)) == null) + { + result.Shadow(hr); + result.RefCode = (int)HttpCode.USER_UNDEF; + result.RefText += string.Format("[{0}] [ResumableUpload] mkblk Error: JSON Decode Error: text = {1}\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Text); + + return result; + } + + context = rc.Ctx; + offset += chunkSize; + leftBytes -= chunkSize; + + #endregion mkblk + putExtra.ProgressHandler(offset, fileSize); + + if (leftBytes > 0) + { + blockLeft = blockSize - chunkSize; + blockOffset = chunkSize; + while (blockLeft > 0) + { + #region bput-loop + + if (blockLeft < CHUNK_SIZE) { - result.RefCode = (int)HttpCode.USER_PAUSED; - result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is paused\n", - DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); - manualResetEvent.WaitOne(1000); + chunkSize = blockLeft; } - else if (upCtrl == UploadControllerAction.Activated) + else { - break; + chunkSize = CHUNK_SIZE; } - } - long offset = blockIndex * BLOCK_SIZE; - stream.Seek(offset, SeekOrigin.Begin); - int blockLen = stream.Read(blockBuffer, 0, BLOCK_SIZE); - byte[] blockData = new byte[blockLen]; - Array.Copy(blockBuffer, blockData, blockLen); - blockDataDict.Add(blockIndex, blockData); + stream.Read(chunkBuffer, 0, (int)chunkSize); - if (blockDataDict.Count == putExtra.BlockUploadThreads) - { - processMakeBlocks(blockDataDict, upToken, putExtra, resumeInfo, blockMakeResults, uploadedBytesDict, fileSize); - //check mkblk results - foreach (int blkIndex in blockMakeResults.Keys) + iTry = 0; + while (++iTry <= putExtra.MaxRetryTimes) { - HttpResult mkblkRet = blockMakeResults[blkIndex]; - if (mkblkRet.Code != 200) + result.RefText += string.Format("[{0}] [ResumableUpload] try bput#{1}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), iTry); + + hr = BputChunk(chunkBuffer, blockOffset, chunkSize, context, upToken); + + if (hr.Code == (int)HttpCode.OK && hr.RefCode != (int)HttpCode.USER_NEED_RETRY) { - result = mkblkRet; - manualResetEvent.Set(); - return result; + break; } } - blockDataDict.Clear(); - blockMakeResults.Clear(); - if (!string.IsNullOrEmpty(putExtra.ResumeRecordFile)) + if (hr.Code != (int)HttpCode.OK || hr.RefCode == (int)HttpCode.USER_NEED_RETRY) { - ResumeHelper.Save(resumeInfo, putExtra.ResumeRecordFile); + result.Shadow(hr); + result.RefText += string.Format("[{0}] [ResumableUpload] Error: bput: code = {1}, text = {2}, offset = {3}, blockOffset = {4}, chunkSize = {5}\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Code, hr.Text, offset, blockOffset, chunkSize); + + return result; } - } - } - } - if (blockDataDict.Count > 0) - { - processMakeBlocks(blockDataDict, upToken, putExtra, resumeInfo, blockMakeResults, uploadedBytesDict, fileSize); - //check mkblk results - foreach (int blkIndex in blockMakeResults.Keys) - { - HttpResult mkblkRet = blockMakeResults[blkIndex]; - if (mkblkRet.Code != 200) - { - result = mkblkRet; - manualResetEvent.Set(); - return result; + if ((rc=JsonConvert.DeserializeObject(hr.Text))==null) + { + result.Shadow(hr); + result.RefCode = (int)HttpCode.USER_UNDEF; + result.RefText += string.Format("[{0}] [ResumableUpload] bput Error: JSON Decode Error: text = {1}\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Text); + + return result; + } + + context = rc.Ctx; + if (expiredAt == 0) + { + expiredAt = rc.Expired_At; + } + + offset += chunkSize; + leftBytes -= chunkSize; + blockOffset += chunkSize; + blockLeft -= chunkSize; + #endregion bput-loop + + putExtra.ProgressHandler(offset, fileSize); } } - blockDataDict.Clear(); - blockMakeResults.Clear(); + + #endregion one-block + + resumeInfo.BlockIndex = index; + resumeInfo.Contexts[index] = context; + resumeInfo.ExpiredAt = expiredAt; if (!string.IsNullOrEmpty(putExtra.ResumeRecordFile)) { ResumeHelper.Save(resumeInfo, putExtra.ResumeRecordFile); } + ++index; } + } - if (upCtrl == UploadControllerAction.Activated) - { - HttpResult hr = MakeFile(key, fileSize, key, upToken, putExtra, resumeInfo.Contexts); - if (hr.Code != (int)HttpCode.OK) - { - result.Shadow(hr); - result.RefText += string.Format("[{0}] [ResumableUpload] Error: mkfile: code = {1}, text = {2}\n", - DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Code, hr.Text); - } - - if (File.Exists(putExtra.ResumeRecordFile)) - { - File.Delete(putExtra.ResumeRecordFile); - } - result.Shadow(hr); - result.RefText += string.Format("[{0}] [ResumableUpload] Uploaded: \"{1}\" ==> \"{2}\"\n", - DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), putExtra.ResumeRecordFile, key); - } - else - { - result.Code = (int)HttpCode.USER_CANCELED; - result.RefCode = (int)HttpCode.USER_CANCELED; - result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is aborted, mkfile\n", - DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); - } + hr = MakeFile(key, fileSize, key, upToken, putExtra, resumeInfo.Contexts); + if (hr.Code != (int)HttpCode.OK) + { + result.Shadow(hr); + result.RefText += string.Format("[{0}] [ResumableUpload] Error: mkfile: code = {1}, text = {2}\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Code, hr.Text); - manualResetEvent.Set(); return result; } - catch (Exception ex) - { - Console.WriteLine(ex.StackTrace); - StringBuilder sb = new StringBuilder(); - sb.AppendFormat("[{0}] [ResumableUpload] Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); - Exception e = ex; - while (e != null) - { - sb.Append(e.Message + " "); - e = e.InnerException; - } - sb.AppendLine(); - result.RefCode = (int)HttpCode.USER_UNDEF; - result.RefText += sb.ToString(); + if (File.Exists(putExtra.ResumeRecordFile)) + { + File.Delete(putExtra.ResumeRecordFile); } + result.Shadow(hr); + result.RefText += string.Format("[{0}] [ResumableUpload] Uploaded: \"{1}\" ==> \"{2}\"\n", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), putExtra.ResumeRecordFile, key); } - - return result; - } - - private void processMakeBlocks(Dictionary blockDataDict, string upToken, - PutExtra putExtra, ResumeInfo resumeInfo, Dictionary blockMakeResults, - Dictionary uploadedBytesDict, long fileSize) - { - int taskMax = blockDataDict.Count; - ManualResetEvent[] doneEvents = new ManualResetEvent[taskMax]; - int eventIndex = 0; - object progressLock = new object(); - foreach (long blockIndex in blockDataDict.Keys) + catch (Exception ex) { - //signal task - ManualResetEvent doneEvent = new ManualResetEvent(false); - doneEvents[eventIndex] = doneEvent; - eventIndex += 1; - - //queue task - byte[] blockData = blockDataDict[blockIndex]; - ResumeBlocker resumeBlocker = new ResumeBlocker(doneEvent, blockData, blockIndex, upToken, putExtra, - resumeInfo, blockMakeResults, progressLock, uploadedBytesDict, fileSize); - ThreadPool.QueueUserWorkItem(new WaitCallback(this.MakeBlock), resumeBlocker); - } + Console.WriteLine(ex.StackTrace); + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("[{0}] [ResumableUpload] Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); + Exception e = ex; + while (e != null) + { + sb.Append(e.Message + " "); + e = e.InnerException; + } + sb.AppendLine(); - try - { - WaitHandle.WaitAll(doneEvents); + result.RefCode = (int)HttpCode.USER_UNDEF; + result.RefText += sb.ToString(); } - catch (Exception ex) + finally { - Console.WriteLine("wait all exceptions:" + ex.StackTrace); - //pass + if (stream != null) + { + stream.Close(); + stream.Dispose(); + } } + + return result; } /// - /// 创建块(携带首片数据),同时检查CRC32 + /// 根据已上传的所有分片数据创建文件 /// - /// 创建分片上次的块请求 - private void MakeBlock(object resumeBlockerObj) + /// 源文件名 + /// 文件大小 + /// 要保存的文件名 + /// 所有数据块的Context + /// 上传凭证 + /// 用户指定的额外参数 + /// 此操作执行后的返回结果 + private HttpResult MakeFile(string fileName, long size, string key, string upToken, PutExtra putExtra, string[] contexts) { - ResumeBlocker resumeBlocker = (ResumeBlocker)resumeBlockerObj; - ManualResetEvent doneEvent = resumeBlocker.DoneEvent; - Dictionary blockMakeResults = resumeBlocker.BlockMakeResults; - PutExtra putExtra = resumeBlocker.PutExtra; - long blockIndex = resumeBlocker.BlockIndex; HttpResult result = new HttpResult(); - //check whether to cancel - while (true) + + try + { + string fnameStr = "fname"; + string mimeTypeStr = ""; + string keyStr = ""; + string paramStr = ""; + //check file name + if (!string.IsNullOrEmpty(fileName)) + { + fnameStr = string.Format("/fname/{0}", Base64.UrlSafeBase64Encode(fileName)); + } + + //check mime type + if (!string.IsNullOrEmpty(putExtra.MimeType)) + { + mimeTypeStr = string.Format("/mimeType/{0}", Base64.UrlSafeBase64Encode(putExtra.MimeType)); + } + + //check key + if (!string.IsNullOrEmpty(key)) + { + keyStr = string.Format("/key/{0}", Base64.UrlSafeBase64Encode(key)); + } + + //check extra params + if (putExtra.Params != null && putExtra.Params.Count > 0) + { + StringBuilder sb = new StringBuilder(); + foreach (var kvp in putExtra.Params) + { + string k = kvp.Key; + string v = kvp.Value; + if (k.StartsWith("x:") && !string.IsNullOrEmpty(v)) + { + sb.AppendFormat("/{0}/{1}", k,v); + } + } + + paramStr = sb.ToString(); + } + + //get upload host + string ak = UpToken.GetAccessKeyFromUpToken(upToken); + string bucket = UpToken.GetBucketFromUpToken(upToken); + if (ak == null || bucket == null) + { + return HttpResult.InvalidToken; + } + + string uploadHost = this.config.UpHost(ak, bucket); + + string url = string.Format("{0}/mkfile/{1}{2}{3}{4}{5}", uploadHost, size, mimeTypeStr, fnameStr, keyStr, paramStr); + string body = string.Join(",", contexts); + string upTokenStr = string.Format("UpToken {0}", upToken); + + result = httpManager.PostText(url, body, upTokenStr); + } + catch (Exception ex) { - UploadControllerAction upCtl = resumeBlocker.PutExtra.UploadController(); - if (upCtl == UploadControllerAction.Suspended) + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("[{0}] mkfile Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); + Exception e = ex; + while (e != null) { - doneEvent.WaitOne(1000); - continue; + sb.Append(e.Message + " "); + e = e.InnerException; } - else if (upCtl == UploadControllerAction.Aborted) + sb.AppendLine(); + + if (ex is QiniuException) { - doneEvent.Set(); - - result.Code = (int)HttpCode.USER_CANCELED; - result.RefCode = (int)HttpCode.USER_CANCELED; - result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is aborted, mkblk {1}\n", - DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), blockIndex); - blockMakeResults.Add(blockIndex, result); - return; + QiniuException qex = (QiniuException)ex; + result.Code = qex.HttpResult.Code; + result.RefCode = qex.HttpResult.Code; + result.Text = qex.HttpResult.Text; + result.RefText += sb.ToString(); } else { - break; + result.RefCode = (int)HttpCode.USER_UNDEF; + result.RefText += sb.ToString(); } } - byte[] blockBuffer = resumeBlocker.BlockBuffer; - int blockSize = blockBuffer.Length; + return result; + } - string upToken = resumeBlocker.UploadToken; - Dictionary uploadedBytesDict = resumeBlocker.UploadedBytesDict; - long fileSize = resumeBlocker.FileSize; - object progressLock = resumeBlocker.ProgressLock; - ResumeInfo resumeInfo = resumeBlocker.ResumeInfo; + /// + /// 创建块(携带首片数据),同时检查CRC32 + /// + /// 数据片,此操作都会携带第一个数据片 + /// 块大小,除了最后一块可能不足4MB,前面的所有数据块恒定位4MB + /// 分片大小,一个块可以被分为若干片依次上传然后拼接或者不分片直接上传整块 + /// 上传凭证 + /// 此操作执行后的返回结果 + private HttpResult MakeBlock(byte[] chunkBuffer, long blockSize, long chunkSize, string upToken) + { + HttpResult result = new HttpResult(); try { @@ -379,16 +507,14 @@ private void MakeBlock(object resumeBlockerObj) string bucket = UpToken.GetBucketFromUpToken(upToken); if (ak == null || bucket == null) { - result = HttpResult.InvalidToken; - doneEvent.Set(); - return; + return HttpResult.InvalidToken; } string uploadHost = this.config.UpHost(ak, bucket); string url = string.Format("{0}/mkblk/{1}", uploadHost, blockSize); string upTokenStr = string.Format("UpToken {0}", upToken); - using (MemoryStream ms = new MemoryStream(blockBuffer, 0, blockSize)) + using (MemoryStream ms = new MemoryStream(chunkBuffer, 0, (int)chunkSize)) { byte[] data = ms.ToArray(); @@ -401,23 +527,12 @@ private void MakeBlock(object resumeBlockerObj) if (rc.Crc32 > 0) { uint crc_1 = rc.Crc32; - uint crc_2 = CRC32.CheckSumSlice(blockBuffer, 0, blockSize); + uint crc_2 = CRC32.CheckSumSlice(chunkBuffer, 0, (int)chunkSize); if (crc_1 != crc_2) { result.RefCode = (int)HttpCode.USER_NEED_RETRY; result.RefText += string.Format(" CRC32: remote={0}, local={1}\n", crc_1, crc_2); } - else - { - //write the mkblk context - resumeInfo.Contexts[blockIndex] = rc.Ctx; - resumeInfo.ExpiredAt = rc.ExpiredAt; - lock (progressLock) - { - uploadedBytesDict["UploadProgress"] += blockSize; - } - putExtra.ProgressHandler(uploadedBytesDict["UploadProgress"], fileSize); - } } else { @@ -459,66 +574,25 @@ private void MakeBlock(object resumeBlockerObj) } } - //return the http result - blockMakeResults.Add(blockIndex, result); - doneEvent.Set(); + return result; } + /// - /// 根据已上传的所有分片数据创建文件 + /// 上传数据片,同时检查CRC32 /// - /// 源文件名 - /// 文件大小 - /// 要保存的文件名 - /// 所有数据块的Context + /// 数据片 + /// 当前片在块中的偏移位置 + /// 当前片的大小 + /// 承接前一片数据用到的Context /// 上传凭证 - /// 用户指定的额外参数 /// 此操作执行后的返回结果 - private HttpResult MakeFile(string fileName, long size, string key, string upToken, PutExtra putExtra, string[] contexts) + private HttpResult BputChunk(byte[] chunkBuffer, long offset, long chunkSize, string context, string upToken) { HttpResult result = new HttpResult(); try { - string fnameStr = "fname"; - string mimeTypeStr = ""; - string keyStr = ""; - string paramStr = ""; - //check file name - if (!string.IsNullOrEmpty(fileName)) - { - fnameStr = string.Format("/fname/{0}", Base64.UrlSafeBase64Encode(fileName)); - } - - //check mime type - if (!string.IsNullOrEmpty(putExtra.MimeType)) - { - mimeTypeStr = string.Format("/mimeType/{0}", Base64.UrlSafeBase64Encode(putExtra.MimeType)); - } - - //check key - if (!string.IsNullOrEmpty(key)) - { - keyStr = string.Format("/key/{0}", Base64.UrlSafeBase64Encode(key)); - } - - //check extra params - if (putExtra.Params != null && putExtra.Params.Count > 0) - { - StringBuilder sb = new StringBuilder(); - foreach (var kvp in putExtra.Params) - { - string k = kvp.Key; - string v = kvp.Value; - if (k.StartsWith("x:") && !string.IsNullOrEmpty(v)) - { - sb.AppendFormat("/{0}/{1}", k, v); - } - } - - paramStr = sb.ToString(); - } - //get upload host string ak = UpToken.GetAccessKeyFromUpToken(upToken); string bucket = UpToken.GetBucketFromUpToken(upToken); @@ -529,16 +603,45 @@ private HttpResult MakeFile(string fileName, long size, string key, string upTok string uploadHost = this.config.UpHost(ak, bucket); - string url = string.Format("{0}/mkfile/{1}{2}{3}{4}{5}", uploadHost, size, mimeTypeStr, fnameStr, keyStr, paramStr); - string body = string.Join(",", contexts); + string url = string.Format("{0}/bput/{1}/{2}", uploadHost, context, offset); string upTokenStr = string.Format("UpToken {0}", upToken); - result = httpManager.PostText(url, body, upTokenStr); + using (MemoryStream ms = new MemoryStream(chunkBuffer, 0, (int)chunkSize)) + { + byte[] data = ms.ToArray(); + + result = httpManager.PostData(url, data, upTokenStr); + + if (result.Code == (int)HttpCode.OK) + { + Dictionary rd=JsonConvert.DeserializeObject>(result.Text); + if (rd.ContainsKey("crc32")) + { + uint crc_1 = Convert.ToUInt32(rd["crc32"]); + uint crc_2 = CRC32.CheckSumSlice(chunkBuffer, 0, (int)chunkSize); + if (crc_1 != crc_2) + { + result.RefCode = (int)HttpCode.USER_NEED_RETRY; + result.RefText += string.Format(" CRC32: remote={0}, local={1}\n", crc_1, crc_2); + } + } + else + { + result.RefText += string.Format("[{0}] JSON Decode Error: text = {1}", + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), result.Text); + result.RefCode = (int)HttpCode.USER_NEED_RETRY; + } + } + else + { + result.RefCode = (int)HttpCode.USER_NEED_RETRY; + } + } } catch (Exception ex) { StringBuilder sb = new StringBuilder(); - sb.AppendFormat("[{0}] mkfile Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); + sb.AppendFormat("[{0}] bput Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); Exception e = ex; while (e != null) { @@ -565,7 +668,6 @@ private HttpResult MakeFile(string fileName, long size, string key, string upTok return result; } - /// /// 默认的进度处理函数-上传文件 /// @@ -582,7 +684,7 @@ public static void DefaultUploadProgressHandler(long uploadedBytes, long totalBy Console.WriteLine("[{0}] [ResumableUpload] Progress: {1,7:0.000}%\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), 100.0); } } - + /// /// 默认的上传控制函数,默认不执行任何控制 /// diff --git a/src/Qiniu/Storage/ResumeBlocker.cs b/src/Qiniu/Storage/ResumeBlocker.cs index 46a2b232..1dd79025 100644 --- a/src/Qiniu/Storage/ResumeBlocker.cs +++ b/src/Qiniu/Storage/ResumeBlocker.cs @@ -1,4 +1,4 @@ -using Qiniu.Http; +using Qiniu.Http; using System.Collections.Generic; using System.Threading; @@ -33,4 +33,4 @@ public ResumeBlocker(ManualResetEvent doneEvent, byte[] blockBuffer, long blockI this.FileSize = fileSize; } } -} +} \ No newline at end of file diff --git a/src/Qiniu/Storage/ResumeContext.cs b/src/Qiniu/Storage/ResumeContext.cs index 452ba5b1..57b76db6 100644 --- a/src/Qiniu/Storage/ResumeContext.cs +++ b/src/Qiniu/Storage/ResumeContext.cs @@ -40,6 +40,6 @@ public class ResumeContext /// ctx失效时刻 /// [JsonProperty("expired_at")] - public long ExpiredAt { get; set; } + public long Expired_At { get; set; } } } diff --git a/src/Qiniu/Storage/ResumeInfo.cs b/src/Qiniu/Storage/ResumeInfo.cs index 86377af8..64e912da 100644 --- a/src/Qiniu/Storage/ResumeInfo.cs +++ b/src/Qiniu/Storage/ResumeInfo.cs @@ -12,11 +12,17 @@ public class ResumeInfo [JsonProperty("fileSize")] public long FileSize { get; set; } + /// + /// 当前块编号 + /// + [JsonProperty("blockIndex")] + public int BlockIndex { get; set; } + /// /// 文件块总数 /// [JsonProperty("blockCount")] - public long BlockCount { get; set; } + public int BlockCount { get; set; } /// /// 上下文信息列表 diff --git a/src/Qiniu/Storage/Tokens.cs b/src/Qiniu/Storage/Tokens.cs new file mode 100644 index 00000000..2e0ce18f --- /dev/null +++ b/src/Qiniu/Storage/Tokens.cs @@ -0,0 +1,56 @@ +using System; +using System.Text; +using Qiniu.Util; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NUnit.Framework; + +namespace Qiniu.Storage +{ + /// + /// Tokens + /// + public class Tokens + { + /// + /// getQboxToken + /// + /// void + public void getQiniuToken() + { + // input url + string strUrl = ""; + // input method(POST/GET) + string method = ""; + // input byte[] body + JObject jsonBody = new JObject(); + string jsonobj = JsonConvert.SerializeObject(jsonBody); + byte[] body = Encoding.UTF8.GetBytes(jsonobj); + //input contentType + string contentType = ""; + + Mac mac = new Mac("", ""); + Auth auth = new Auth(mac); + string qiniuToken = Auth.CreateQiniuToken(mac,strUrl, method, body, contentType); + Console.WriteLine(qiniuToken); + } + /// + /// getQboxToken + /// + /// void + public void getQboxToken() + { + // input url + string strUrl = ""; + //input body + JObject jsonBody = new JObject(); + string jsonobj = JsonConvert.SerializeObject(jsonBody); + byte[] body = Encoding.UTF8.GetBytes(jsonobj); + + Mac mac = new Mac("", ""); + Auth auth = new Auth(mac); + string qboxToken = Auth.CreateManageToken(mac,strUrl, body); + Console.WriteLine(qboxToken); + } + } +} diff --git a/src/Qiniu/Storage/Zone.cs b/src/Qiniu/Storage/Zone.cs index 63b17027..a228e6b5 100644 --- a/src/Qiniu/Storage/Zone.cs +++ b/src/Qiniu/Storage/Zone.cs @@ -1,107 +1,29 @@ -namespace Qiniu.Storage +using System; +namespace Qiniu.Storage { /// - /// 目前已支持的区域:华东/华北/华南/北美/新加坡 + /// 目前已支持的区域:华东/华北/华南/北美 /// - public class Zone + public class Zone:Region { - /// - /// 资源管理 - /// - public string RsHost { set; get; } - - /// - /// 源列表 - /// - public string RsfHost { set; get; } - - /// - /// 数据处理 - /// - public string ApiHost { set; get; } - - /// - /// 镜像刷新、资源抓取 - /// - public string IovipHost { set; get; } - - /// - /// 资源上传 - /// - public string[] SrcUpHosts { set; get; } - - /// - /// CDN加速 - /// - public string[] CdnUpHosts { set; get; } - /// /// 华东 /// - public static Zone ZONE_CN_East = new Zone() - { - RsHost = "rs.qbox.me", - RsfHost = "rsf.qbox.me", - ApiHost = "api.qiniu.com", - IovipHost = "iovip.qbox.me", - SrcUpHosts = new string[] { "up.qiniup.com", - "up-nb.qiniup.com", "up-xs.qiniup.com" }, - CdnUpHosts = new string[] { "upload.qiniup.com", - "upload-nb.qiniup.com", "upload-xs.qiniup.com" } - }; + public static Region ZONE_CN_East = Region_CN_East; /// /// 华北 /// - public static Zone ZONE_CN_North = new Zone() - { - RsHost = "rs-z1.qbox.me", - RsfHost = "rsf-z1.qbox.me", - ApiHost = "api-z1.qiniu.com", - IovipHost = "iovip-z1.qbox.me", - SrcUpHosts = new string[] { "up-z1.qiniup.com" }, - CdnUpHosts = new string[] { "upload-z1.qiniup.com" } - }; + public static Region ZONE_CN_North = Region_CN_North; /// /// 华南 /// - public static Zone ZONE_CN_South = new Zone() - { - RsHost = "rs-z2.qbox.me", - RsfHost = "rsf-z2.qbox.me", - ApiHost = "api-z2.qiniu.com", - IovipHost = "iovip-z2.qbox.me", - SrcUpHosts = new string[] { "up-z2.qiniup.com", - "up-gz.qiniup.com", "up-fs.qiniup.com" }, - CdnUpHosts = new string[] { "upload-z2.qiniup.com", - "upload-gz.qiniup.com", "upload-fs.qiniup.com" } - }; + public static Region ZONE_CN_South = Region_CN_South; /// /// 北美 /// - public static Zone ZONE_US_North = new Zone() - { - RsHost = "rs-na0.qbox.me", - RsfHost = "rsf-na0.qbox.me", - ApiHost = "api-na0.qiniu.com", - IovipHost = "iovip-na0.qbox.me", - SrcUpHosts = new string[] { "up-na0.qiniup.com" }, - CdnUpHosts = new string[] { "upload-na0.qiniup.com" } - }; - - /// - /// 新加坡 - /// - public static Zone ZONE_AS_Singapore = new Zone() - { - RsHost = "rs-as0.qbox.me", - RsfHost = "rsf-as0.qbox.me", - ApiHost = "api-as0.qiniu.com", - IovipHost = "iovip-as0.qbox.me", - SrcUpHosts = new string[] { "up-as0.qiniup.com" }, - CdnUpHosts = new string[] { "upload-as0.qiniup.com" } - }; + public static Region ZONE_US_North = Region_US_North; } } diff --git a/src/Qiniu/Storage/ZoneHelper.cs b/src/Qiniu/Storage/ZoneHelper.cs deleted file mode 100644 index ebcc2375..00000000 --- a/src/Qiniu/Storage/ZoneHelper.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Text; -using System.Collections.Generic; -using Qiniu.Http; -using Newtonsoft.Json; - -namespace Qiniu.Storage -{ - /// - /// Zone辅助类,查询及配置Zone - /// - public class ZoneHelper - { - private static Dictionary zoneCache = new Dictionary(); - private static object rwLock = new object(); - - /// - /// 从uc.qbox.me查询得到回复后,解析出upHost,然后根据upHost确定Zone - /// - /// AccessKek - /// 空间名称 - public static Zone QueryZone(string accessKey, string bucket) - { - Zone zone = null; - - string cacheKey = string.Format("{0}:{1}", accessKey, bucket); - - //check from cache - lock (rwLock) - { - if (zoneCache.ContainsKey(cacheKey)) - { - zone = zoneCache[cacheKey]; - } - } - - if (zone != null) - { - return zone; - } - - //query from uc api - HttpResult hr = null; - try - { - string queryUrl = string.Format("https://uc.qbox.me/v2/query?ak={0}&bucket={1}", accessKey, bucket); - HttpManager httpManager = new HttpManager(); - hr = httpManager.Get(queryUrl, null); - if (hr.Code == (int)HttpCode.OK) - { - ZoneInfo zInfo = JsonConvert.DeserializeObject(hr.Text); - if (zInfo != null) - { - zone = new Zone(); - zone.SrcUpHosts = zInfo.Up.Src.Main; - zone.CdnUpHosts = zInfo.Up.Acc.Main; - zone.IovipHost = zInfo.Io.Src.Main[0]; - if (zone.IovipHost.Contains("z1")) - { - zone.ApiHost = "api-z1.qiniu.com"; - zone.RsHost = "rs-z1.qiniu.com"; - zone.RsfHost = "rsf-z1.qiniu.com"; - } - else if (zone.IovipHost.Contains("z2")) - { - zone.ApiHost = "api-z2.qiniu.com"; - zone.RsHost = "rs-z2.qiniu.com"; - zone.RsfHost = "rsf-z2.qiniu.com"; - } - else if (zone.IovipHost.Contains("na0")) - { - zone.ApiHost = "api-na0.qiniu.com"; - zone.RsHost = "rs-na0.qiniu.com"; - zone.RsfHost = "rsf-na0.qiniu.com"; - } - else if (zone.IovipHost.Contains("as0")) - { - zone.ApiHost = "api-as0.qiniu.com"; - zone.RsHost = "rs-as0.qiniu.com"; - zone.RsfHost = "rsf-as0.qiniu.com"; - } - else - { - zone.ApiHost = "api.qiniu.com"; - zone.RsHost = "rs.qiniu.com"; - zone.RsfHost = "rsf.qiniu.com"; - } - - lock (rwLock) - { - zoneCache[cacheKey] = zone; - } - } - else - { - throw new Exception("JSON Deserialize failed: " + hr.Text); - } - } - else - { - throw new Exception("code: " + hr.Code + ", text: " + hr.Text + ", ref-text:" + hr.RefText); - } - } - catch (Exception ex) - { - StringBuilder sb = new StringBuilder(); - sb.AppendFormat("[{0}] QueryZone Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); - Exception e = ex; - while (e != null) - { - sb.Append(e.Message + " "); - e = e.InnerException; - } - sb.AppendLine(); - - throw new QiniuException(hr, sb.ToString()); - } - - return zone; - } - } - -} diff --git a/src/Qiniu/Util/Auth.cs b/src/Qiniu/Util/Auth.cs index 0fe4934f..fa8df804 100644 --- a/src/Qiniu/Util/Auth.cs +++ b/src/Qiniu/Util/Auth.cs @@ -27,18 +27,19 @@ public Auth(Mac mac) public string CreateManageToken(string url,byte[] body) { return string.Format("QBox {0}", signature.SignRequest(url, body)); - } - + } + /// /// 生成管理凭证-不包含body /// - /// 请求的URL + /// 请求的URL /// 生成的管理凭证 public string CreateManageToken(string url) { return CreateManageToken(url, null); - } - + } + + /// /// 生成上传凭证 /// @@ -94,6 +95,17 @@ public static string CreateManageToken(Mac mac, string url, byte[] body) { Signature sx = new Signature(mac); return string.Format("QBox {0}", sx.SignRequest(url, body)); + } + /// 账号(密钥) + /// 访问的URL + /// 访问的URL + /// 请求的body + /// 请求的body + /// 生成的管理凭证 + public static string CreateQiniuToken(Mac mac, string url, string method, byte[] body, string contentType) + { + Signature sx = new Signature(mac); + return string.Format("Qiniu {0}",sx.SignRequest(url, method, body,contentType)); } /// diff --git a/src/Qiniu/Util/Signature.cs b/src/Qiniu/Util/Signature.cs index 37919a60..d05785ab 100644 --- a/src/Qiniu/Util/Signature.cs +++ b/src/Qiniu/Util/Signature.cs @@ -1,126 +1,193 @@ -using System; -using System.IO; -#if WINDOWS_UWP -using Windows.Security.Cryptography; -using Windows.Security.Cryptography.Core; -#else -using System.Security.Cryptography; -#endif -using System.Text; - -namespace Qiniu.Util -{ - /// - /// 签名/加密 - /// 特别注意,不同平台使用的Cryptography可能略有不同,使用中如有遇到问题,请反馈 - /// 提交您的issue到 https://github.com/qiniu/csharp-sdk - /// - public class Signature - { - private Mac mac; - - /// - /// 初始化 - /// - /// 账号(密钥) - public Signature(Mac mac) - { - this.mac = mac; - } - - private string encodedSign(byte[] data) - { - HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(mac.SecretKey)); - byte[] digest = hmac.ComputeHash(data); - return Base64.UrlSafeBase64Encode(digest); - } - - private string encodedSign(string str) - { - byte[] data = Encoding.UTF8.GetBytes(str); - return encodedSign(data); - } - - /// - /// 签名-字节数据 - /// - /// 待签名的数据 - /// - public string Sign(byte[] data) - { - return string.Format("{0}:{1}", mac.AccessKey, encodedSign(data)); - } - - /// - /// 签名-字符串数据 - /// - /// 待签名的数据 - /// - public string Sign(string str) - { - byte[] data = Encoding.UTF8.GetBytes(str); - return Sign(data); - } - - /// - /// 附带数据的签名 - /// - /// 待签名的数据 - /// - public string SignWithData(byte[] data) - { - string sstr = Base64.UrlSafeBase64Encode(data); - return string.Format("{0}:{1}:{2}", mac.AccessKey, encodedSign(sstr), sstr); - } - - /// - /// 附带数据的签名 - /// - /// 待签名的数据 - /// 签名结果 - public string SignWithData(string str) - { - byte[] data = Encoding.UTF8.GetBytes(str); - return SignWithData(data); - } - - /// - /// HTTP请求签名 - /// - /// 请求目标的URL - /// 请求的主体数据 - /// - public string SignRequest(string url, byte[] body) - { - Uri u = new Uri(url); - string pathAndQuery = u.PathAndQuery; - byte[] pathAndQueryBytes = Encoding.UTF8.GetBytes(pathAndQuery); - - using (MemoryStream buffer = new MemoryStream()) - { - buffer.Write(pathAndQueryBytes, 0, pathAndQueryBytes.Length); - buffer.WriteByte((byte)'\n'); - if (body != null && body.Length > 0) - { - buffer.Write(body, 0, body.Length); - } - HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(mac.SecretKey)); - byte[] digest = hmac.ComputeHash(buffer.ToArray()); - string digestBase64 = Base64.UrlSafeBase64Encode(digest); - return string.Format("{0}:{1}", mac.AccessKey, digestBase64); - } - } - - /// - /// HTTP请求签名 - /// - /// 请求目标的URL - /// 请求的主体数据 - /// - public string SignRequest(string url, string body) - { - byte[] data = Encoding.UTF8.GetBytes(body); - return SignRequest(url, data); - } - } -} +using System; +using System.IO; +#if WINDOWS_UWP +using Windows.Security.Cryptography; +using Windows.Security.Cryptography.Core; +#else +using System.Security.Cryptography; +#endif +using System.Text; +using System.Collections.Generic; +using System.Net; +using Newtonsoft.Json; + +namespace Qiniu.Util +{ + /// + /// 签名/加密 + /// 特别注意,不同平台使用的Cryptography可能略有不同,使用中如有遇到问题,请反馈 + /// 提交您的issue到 https://github.com/qiniu/csharp-sdk + /// + public class Signature + { + private Mac mac; + /// + /// 初始化 + /// + /// 账号(密钥) + public Signature(Mac mac) + { + this.mac = mac; + } + + private string encodedSign(byte[] data) + { +#if WINDOWS_UWP + var hma = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha1); + var skBuffer = CryptographicBuffer.ConvertStringToBinary(mac.SecretKey, BinaryStringEncoding.Utf8); + var hmacKey = hma.CreateKey(skBuffer); + var dataBuffer = CryptographicBuffer.CreateFromByteArray(data); + var signBuffer = CryptographicEngine.Sign(hmacKey, dataBuffer); + byte[] digest; + CryptographicBuffer.CopyToByteArray(signBuffer, out digest); +#else + HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(mac.SecretKey)); + byte[] digest = hmac.ComputeHash(data); +#endif + return Base64.UrlSafeBase64Encode(digest); + } + + private string encodedSign(string str) + { + byte[] data = Encoding.UTF8.GetBytes(str); + return encodedSign(data); + } + + /// + /// 签名-字节数据 + /// + /// 待签名的数据 + /// + public string Sign(byte[] data) + { + return string.Format("{0}:{1}", mac.AccessKey,encodedSign(data)); + } + + /// + /// 签名-字符串数据 + /// + /// 待签名的数据 + /// + public string Sign(string str) + { + byte[] data = Encoding.UTF8.GetBytes(str); + return Sign(data); + } + + /// + /// 附带数据的签名 + /// + /// 待签名的数据 + /// + public string SignWithData(byte[] data) + { + string sstr = Base64.UrlSafeBase64Encode(data); + return string.Format("{0}:{1}:{2}", mac.AccessKey, encodedSign(sstr), sstr); + } + + /// + /// 附带数据的签名ttttt + /// + /// 待签名的数据 + /// 签名结果 + public string SignWithData(string str) + { + byte[] data = Encoding.UTF8.GetBytes(str); + return SignWithData(data); + } + + /// + /// HTTP请求签名 + /// + /// 请求目标的URL + /// 请求的主体数据 + /// + public string SignRequest(string url, byte[] body) + { + Uri u = new Uri(url); + string pathAndQuery = u.PathAndQuery; + byte[] pathAndQueryBytes = Encoding.UTF8.GetBytes(pathAndQuery); + + using (MemoryStream buffer = new MemoryStream()) + { + buffer.Write(pathAndQueryBytes, 0, pathAndQueryBytes.Length); + buffer.WriteByte((byte)'\n'); + if (body != null && body.Length > 0) + { + buffer.Write(body, 0, body.Length); + } +#if WINDOWS_UWP + var hma = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha1); + var skBuffer = CryptographicBuffer.ConvertStringToBinary(mac.SecretKey, BinaryStringEncoding.Utf8); + var hmacKey = hma.CreateKey(skBuffer); + var dataBuffer = CryptographicBuffer.CreateFromByteArray(buffer.ToArray()); + var signBuffer = CryptographicEngine.Sign(hmacKey, dataBuffer); + byte[] digest; + CryptographicBuffer.CopyToByteArray(signBuffer, out digest); +#else + HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(mac.SecretKey)); + byte[] digest = hmac.ComputeHash(buffer.ToArray()); +#endif + string digestBase64 = Base64.UrlSafeBase64Encode(digest); + return string.Format("{0}:{1}", mac.AccessKey, digestBase64); + } + } + + /// + /// HTTP请求签名 + /// + /// 请求目标的URL + /// 请求的主体数据 + /// + public string SignRequest(string url, string body) + { + byte[] data = Encoding.UTF8.GetBytes(body); + return SignRequest(url, data); + } + + /// + /// HTTP请求签名 + /// + /// 请求目标的URL + /// + /// 请求的主体数据 + /// + /// + + public string SignRequest(string url, string method, byte[] body, string contentType) + { + StringBuilder sb = new StringBuilder(); + Uri u = new Uri(url); + // + string line = string.Format("{0} {1}", method, u.LocalPath); + sb.Append(line); + if (u.Query != "") + { + sb.Append(u.Query); + } + + // Host: + sb.Append(string.Format("\nHost: {0}", u.Host)); + + + + if (u.Port != 80) + { + sb.Append(string.Format(":{0}", u.Port)); + } + // Content-Type: + if (contentType != null) + { + sb.Append(string.Format("\nContent-Type: {0}", contentType)); + } + + // body + sb.Append("\n\n"); + if (body != null && contentType != null && !"application/octet-stream".Equals(contentType)) + { + sb.Append(Encoding.UTF8.GetString((byte[])(object)body, 0, body.Length)); + } + return string.Format("{0}:{1}", mac.AccessKey, encodedSign(sb.ToString())); + } + } +} diff --git a/src/Qiniu/Util/StringHelper.cs b/src/Qiniu/Util/StringHelper.cs index 5b5621d5..20bcdd68 100644 --- a/src/Qiniu/Util/StringHelper.cs +++ b/src/Qiniu/Util/StringHelper.cs @@ -32,11 +32,10 @@ public static string UrlFormEncode(Dictionary values) foreach (KeyValuePair kvp in values) { - urlValuesBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(kvp.Key), Uri.EscapeDataString(kvp.Value)); + urlValuesBuilder.AppendFormat("{0}={1}&", kvp.Key, kvp.Value); } string encodedStr=urlValuesBuilder.ToString(); return encodedStr.Substring(0, encodedStr.Length - 1); } - } } \ No newline at end of file diff --git a/src/Qiniu/packages.config b/src/Qiniu/packages.config deleted file mode 100644 index e382e85c..00000000 --- a/src/Qiniu/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/QiniuTests/CDN/CdnManagerTests.cs b/src/QiniuTests/CDN/CdnManagerTests.cs index 59260ec6..5bf21966 100644 --- a/src/QiniuTests/CDN/CdnManagerTests.cs +++ b/src/QiniuTests/CDN/CdnManagerTests.cs @@ -6,10 +6,16 @@ namespace Qiniu.CDN.Tests { + /// + /// CdnManagerTest + /// [TestFixture] public class CdnManagerTests : TestEnv { - + /// + /// RefreshUrlsAndDirsTest + /// + /// void [Test] public void RefreshUrlsAndDirsTest() { @@ -53,6 +59,10 @@ public void RefreshUrlsAndDirsTest() } } + /// + /// RefreshUrlsTest + /// + /// void [Test] public void RefreshUrlsTest() { @@ -81,7 +91,10 @@ public void RefreshUrlsTest() } } } - + /// + /// RefreshDirsTest + /// + /// void [Test] public void RefreshDirsTest() { @@ -111,7 +124,10 @@ public void RefreshDirsTest() } } - + /// + /// PrefetchUrlsTest + /// + /// void [Test] public void PrefetchUrlsTest() { @@ -140,7 +156,10 @@ public void PrefetchUrlsTest() } } } - + /// + /// GetBandwidthDataTest + /// + /// void [Test] public void GetBandwidthDataTest() { @@ -191,7 +210,10 @@ public void GetBandwidthDataTest() } } } - + /// + /// GetFluxDataTest + /// + /// void [Test] public void GetFluxDataTest() { @@ -243,6 +265,10 @@ public void GetFluxDataTest() } } + /// + /// GetCdnLogListTest + /// + /// void [Test] public void GetCdnLogListTest() { @@ -266,7 +292,10 @@ public void GetCdnLogListTest() } } - + /// + /// CreateTimestampAntiLeechUrlTest + /// + /// void [Test] public void CreateTimestampAntiLeechUrlTest() { diff --git a/src/QiniuTests/QiniuTests.csproj b/src/QiniuTests/QiniuTests.csproj index 1a080619..b229db0e 100644 --- a/src/QiniuTests/QiniuTests.csproj +++ b/src/QiniuTests/QiniuTests.csproj @@ -1,121 +1,112 @@ - - - - Debug - AnyCPU - {E8CB1665-53F7-46A5-9AFD-B85AD08262D0} - Library - Properties - QiniuTests - QiniuTests - v4.0 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - true - full - false - ..\..\bin\ - DEBUG;TRACE - prompt - 4 - ..\..\bin\QiniuTests.xml - - - pdbonly - true - ..\..\bin\ - TRACE - prompt - 4 - ..\..\bin\QiniuTests.xml - - - true - bin\x64\Debug\ - DEBUG;TRACE - ..\..\bin\QiniuTests.xml - full - x64 - prompt - MinimumRecommendedRules.ruleset - - - bin\x64\Release\ - TRACE - ..\..\bin\QiniuTests.xml - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\NUnit.3.8.1\lib\net40\nunit.framework.dll - - - - - - - - - - - - - - - - - - - - - - - - - {2F5B0328-DE8B-4B53-A500-3077E340A51B} - Qiniu - - - - - - - - - - False - - - False - - - False - - - False - - - - - - - + + + + Debug + AnyCPU + {E8CB1665-53F7-46A5-9AFD-B85AD08262D0} + Exe + Properties + QiniuTests + QiniuTests + v4.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + true + full + false + ..\..\bin\ + DEBUG;TRACE + prompt + 4 + ..\..\bin\QiniuTests.xml + false + + + pdbonly + true + ..\..\bin\ + TRACE + prompt + 4 + ..\..\bin\QiniuTests.xml + false + + + + + + + ..\packages\Newtonsoft.Json.11.0.2\lib\net40\Newtonsoft.Json.dll + True + + + False + ..\..\bin\nunit.framework.dll + + + + + + + + + + + + + + + + + + + + + + + + + + {2F5B0328-DE8B-4B53-A500-3077E340A51B} + Qiniu + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + \ No newline at end of file diff --git a/src/QiniuTests/Storage/BucketManagerTests.cs b/src/QiniuTests/Storage/BucketManagerTests.cs index bdc3762a..a9d6dbab 100644 --- a/src/QiniuTests/Storage/BucketManagerTests.cs +++ b/src/QiniuTests/Storage/BucketManagerTests.cs @@ -6,14 +6,22 @@ using System.Collections.Generic; namespace Qiniu.Storage.Tests { + /// + /// BucketManagerTests + /// [TestFixture] public class BucketManagerTests : TestEnv { + /// + /// StatTest + /// + /// void [Test] public void StatTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string key = "qiniu.png"; @@ -29,11 +37,16 @@ public void StatTest() Console.WriteLine(statRet.Result.FileType); } + /// + /// DeleteTest + /// + /// void [Test] public void DeleteTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string newKey = "qiniu-to-delete.png"; @@ -45,11 +58,16 @@ public void DeleteTest() } } + /// + /// CopyTest + /// + /// void [Test] public void CopyTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string newKey = "qiniu-to-copy.png"; @@ -60,12 +78,16 @@ public void CopyTest() } Console.WriteLine(copyRet.ToString()); } - + /// + /// MoveTest + /// + /// void [Test] public void MoveTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string newKey = "qiniu-to-copy.png"; @@ -83,12 +105,16 @@ public void MoveTest() } Console.WriteLine(moveRet.ToString()); } - + /// + /// ChangeMimeTest + /// + /// void [Test] public void ChangeMimeTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); HttpResult ret = bucketManager.ChangeMime(Bucket, "qiniu.png", "image/x-png"); @@ -98,12 +124,16 @@ public void ChangeMimeTest() } Console.WriteLine(ret.ToString()); } - + /// + /// ChangeTypeTest + /// + /// void [Test] public void ChangeTypeTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); HttpResult ret = bucketManager.ChangeType(Bucket, "qiniu.png", 1); @@ -113,12 +143,43 @@ public void ChangeTypeTest() } Console.WriteLine(ret.ToString()); } + /// + /// ChangeStatusTest + /// + /// void + [Test] + public void ChangeStatusTest() + { + Config config = new Config(); + config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; + Mac mac = new Mac(AccessKey, SecretKey); + BucketManager bucketManager = new BucketManager(mac, config); + //条件匹配,只有匹配上才会执行修改操作 + //cond 可以填写 空,一个或者多个 + Dictionary cond = new Dictionary(); + cond.Add("fsize", "186371"); + cond.Add("putTime", "14899798962573916"); + cond.Add("hash", "FiRxWzeeD6ofGTpwTZub5Fx1ozvi"); + cond.Add("mime", "application/vnd.apple.mpegurl"); + HttpResult ret = bucketManager.ChangeStatus(Bucket, "qiniu.png", 1,cond); + if (ret.Code != (int)HttpCode.OK && !ret.Text.Contains("already disabled")) + { + Assert.Fail("change status error: " + ret.ToString()); + } + Console.WriteLine(ret.ToString()); + } + /// + /// DeleteAfterDaysTest + /// + /// void [Test] public void DeleteAfterDaysTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string newKey = "qiniu-to-copy.png"; @@ -136,11 +197,16 @@ public void DeleteAfterDaysTest() Console.WriteLine(expireRet.ToString()); } + /// + /// PrefetchTest + /// + /// void [Test] public void PrefetchTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); HttpResult ret = bucketManager.Prefetch(Bucket, "qiniu.png"); @@ -151,11 +217,16 @@ public void PrefetchTest() Console.WriteLine(ret.ToString()); } + /// + /// DomainsTest + /// + /// void [Test] public void DomainsTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); DomainsResult ret = bucketManager.Domains(Bucket); @@ -166,12 +237,16 @@ public void DomainsTest() Console.WriteLine(ret.ToString()); } - + /// + /// BucketsTest + /// + /// void [Test] public void BucketsTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); BucketsResult ret = bucketManager.Buckets(true); @@ -186,12 +261,16 @@ public void BucketsTest() } } - + /// + /// FetchTest + /// + /// void [Test] public void FetchTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string resUrl = "http://devtools.qiniu.com/qiniu.png"; @@ -210,11 +289,36 @@ public void FetchTest() Console.WriteLine(ret.ToString()); } + /// + /// CreateBucketTest + /// + /// void + [Test] + public void CreateBucketTest() + { + Config config = new Config(); + Mac mac = new Mac(AccessKey, SecretKey); + BucketManager bucketManager = new BucketManager(mac,config); + // 填写存储区域代号 z0:华东 z1:华北 z2:华南 na0:北美 + // Region = "z0" + HttpResult ret = bucketManager.CreateBucket(Bucket, Region); + if (ret.Code != (int)HttpCode.OK) + { + Assert.Fail("create bucket error: " + ret.ToString()); + } + Console.WriteLine(ret.ToString()); + } + + /// + /// ListFilesTest + /// + /// void [Test] public void ListFilesTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string prefix = ""; @@ -229,11 +333,16 @@ public void ListFilesTest() Console.WriteLine(listRet.ToString()); } + /// + /// ListBucketTest + /// + /// void [Test] public void ListBucketTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string prefix = ""; @@ -253,8 +362,41 @@ public void ListBucketTest() } while (!string.IsNullOrEmpty(marker)); } + /// + /// ListBucketV2Test + /// + /// void + [Test] + public void ListBucketV2Test() + { + Config config = new Config(); + config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; + Mac mac = new Mac(AccessKey, SecretKey); + BucketManager bucketManager = new BucketManager(mac, config); + string prefix = ""; + string delimiter = ""; + int limit = 100; + string marker = ""; + do + { + ListResultV2 listRet = bucketManager.ListFilesV2(Bucket, prefix, marker, limit, delimiter); + if (listRet.Code != (int)HttpCode.OK) + { + Assert.Fail("list files error: " + listRet.ToString()); + } + Console.WriteLine(listRet.ToString()); + + marker = listRet.Result[listRet.Result.Length - 1].Marker; + } while (!string.IsNullOrEmpty(marker)); + } + // batch stat, delete, copy, move, chtype, chgm, deleteAfterDays // 批量操作每次不能超过1000个指令 + /// + /// BatchStatTest + /// + /// void [Test] public void BatchStatTest() { @@ -262,6 +404,7 @@ public void BatchStatTest() Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string[] keys = { @@ -295,7 +438,10 @@ public void BatchStatTest() } } } - + /// + /// BatchDeleteTest + /// + /// void [Test] public void BatchDeleteTest() { @@ -303,6 +449,7 @@ public void BatchDeleteTest() Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string[] keys = { @@ -335,12 +482,16 @@ public void BatchDeleteTest() } } } - + /// + /// BatchCopyTest + /// + /// void [Test] public void BatchCopyTest() { Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string[] keys = { @@ -373,7 +524,10 @@ public void BatchCopyTest() } } } - + /// + /// BatchMoveTest + /// + /// void [Test] public void BatchMoveTest() { @@ -381,6 +535,7 @@ public void BatchMoveTest() Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string[] keys = { @@ -413,7 +568,10 @@ public void BatchMoveTest() } } } - + /// + /// BatchChangeMimeTest + /// + /// void [Test] public void BatchChangeMimeTest() { @@ -421,6 +579,7 @@ public void BatchChangeMimeTest() Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string[] keys = { @@ -453,7 +612,10 @@ public void BatchChangeMimeTest() } } } - + /// + /// BatchChangeTypeTest + /// + /// void [Test] public void BatchChangeTypeTest() { @@ -461,6 +623,7 @@ public void BatchChangeTypeTest() Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string[] keys = { @@ -493,7 +656,10 @@ public void BatchChangeTypeTest() } } } - + /// + /// BatchDeleteAfterDaysTes + /// + /// void [Test] public void BatchDeleteAfterDaysTest() { @@ -501,6 +667,7 @@ public void BatchDeleteAfterDaysTest() Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; Mac mac = new Mac(AccessKey, SecretKey); BucketManager bucketManager = new BucketManager(mac, config); string[] keys = { diff --git a/src/QiniuTests/Storage/CensorTests.cs b/src/QiniuTests/Storage/CensorTests.cs new file mode 100644 index 00000000..e15947a9 --- /dev/null +++ b/src/QiniuTests/Storage/CensorTests.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Qiniu.Tests; +using Qiniu.Util; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Net; +using System.IO; +using Qiniu.Tests; +using NUnit.Framework; + +namespace QiniuTests.Storage +{ + /// + /// CensorTests + /// + [TestFixture] + public class CensorTests + { + /// + /// CensorTest + /// + /// void + public static void CensorTest(string[] args) + { + // input accessKey secretKey + string ak = ""; + string sk = ""; + // input url + string strUrl = "http://ai.qiniuapi.com/v3/image/censor"; + string testUrl = ""; + // input method(POST/GET) + string method = "POST"; + // input byte[] body + JObject jsonBody = new JObject(); + JArray array = new JArray() { "pulp", "terror", "politician" }; + JObject scenesJson = new JObject(); + scenesJson["scenes"] = array; + JObject uriJson = new JObject(); + uriJson["uri"] = testUrl; + + jsonBody["data"] = uriJson; + jsonBody["params"] = scenesJson; + + string jsonobj = JsonConvert.SerializeObject(jsonBody); + + Console.WriteLine(jsonBody.ToString()); + + //input contentType + string contentType = "application/json"; + + Mac mac = new Mac(ak, sk); + Auth auth = new Auth(mac); + + byte[] body = Encoding.Default.GetBytes(jsonobj); + + string qiniuToken = Auth.CreateQiniuToken(mac, strUrl, method, body, contentType); + + Console.WriteLine(qiniuToken); + + HttpWebResponse response = null; + try + { + Uri url = new Uri(strUrl); + HttpWebRequest mOkHttpClient = (HttpWebRequest)HttpWebRequest.Create(url); + mOkHttpClient.Method = WebRequestMethods.Http.Post; + mOkHttpClient.ContentType = contentType; + mOkHttpClient.Headers.Add("Authorization", qiniuToken); + mOkHttpClient.ContentLength = body.Length; + using (System.IO.Stream requestStream = mOkHttpClient.GetRequestStream()) + { + CopyN(requestStream, new MemoryStream(body), body.Length); + } + response = (HttpWebResponse)mOkHttpClient.GetResponse(); + + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + Console.Write(e.StackTrace); + throw new Exception(e.Message); + } + + // response never be null + if ((int)response.StatusCode == 200) + { + StreamReader reader = new StreamReader(response.GetResponseStream()); + string text = reader.ReadToEnd(); + JObject jsonObj = JObject.Parse(text); + Console.WriteLine(jsonObj.ToString()); + } + else + { + Console.WriteLine(response.StatusCode); + } + } + + public static void CopyN(Stream dst, Stream src, long numBytesToCopy) + { + int bufferLen = 32 * 1024; + long l = src.Position; + byte[] buffer = new byte[bufferLen]; + long numBytesWritten = 0; + while (numBytesWritten + /// DownloadManagerTests + /// [TestFixture] - public class DownloadManagerTests : TestEnv + public class DownloadManagerTests:TestEnv { + /// + /// CreatePrivateUrlTest + /// + /// void [Test] - public void CreatePrivateUrlTest() + public void CreatePrivateUrlTest() { Mac mac = new Mac(AccessKey, SecretKey); - string domain = "http://if-pri.qiniudn.com"; + string domain = "http://if-pbl.qiniudn.com"; string key = "hello/world/七牛/test.png"; string privateUrl = DownloadManager.CreatePrivateUrl(mac, domain, key, 3600); Console.WriteLine(privateUrl); } + /// + /// CreatePublishUrlTest + /// + /// void [Test] public void CreatePublishUrlTest() { diff --git a/src/QiniuTests/Storage/FormUploaderTests.cs b/src/QiniuTests/Storage/FormUploaderTests.cs index abf85db3..a107ec22 100644 --- a/src/QiniuTests/Storage/FormUploaderTests.cs +++ b/src/QiniuTests/Storage/FormUploaderTests.cs @@ -6,18 +6,23 @@ namespace Qiniu.Storage.Tests { + /// + /// FormUploaderTests + /// [TestFixture] public class FormUploaderTests : TestEnv { + /// + /// UploadFileTest + /// + /// void [Test] public void UploadFileTest() { Mac mac = new Mac(AccessKey, SecretKey); Random rand = new Random(); string key = string.Format("UploadFileTest_{0}.dat", rand.Next()); - string filePath = LocalFile; - PutPolicy putPolicy = new PutPolicy(); putPolicy.Scope = Bucket + ":" + key; putPolicy.SetExpires(3600); @@ -25,6 +30,7 @@ public void UploadFileTest() string token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString()); Config config = new Config(); config.Zone = Zone.ZONE_CN_East; + //config.Region = Region.Region_CN_East; config.UseHttps = true; config.UseCdnDomains = true; config.ChunkSize = ChunkUnit.U512K; diff --git a/src/QiniuTests/Storage/OperationManagerTests.cs b/src/QiniuTests/Storage/OperationManagerTests.cs index 2c75d8c1..c06c2309 100644 --- a/src/QiniuTests/Storage/OperationManagerTests.cs +++ b/src/QiniuTests/Storage/OperationManagerTests.cs @@ -8,10 +8,16 @@ namespace Qiniu.Storage.Tests { + /// + /// OperationManagerTests + /// [TestFixture] public class OperationManagerTests :TestEnv { - + /// + /// PfopTest + /// + /// void [Test] public void PfopTest() { @@ -35,7 +41,10 @@ public void PfopTest() Console.WriteLine(pfopRet.PersistentId); } - + /// + /// PrefopTest + /// + /// void [Test] public void PrefopTest() { diff --git a/src/QiniuTests/Storage/PutPolicyTests.cs b/src/QiniuTests/Storage/PutPolicyTests.cs index 739bd76c..273930f5 100644 --- a/src/QiniuTests/Storage/PutPolicyTests.cs +++ b/src/QiniuTests/Storage/PutPolicyTests.cs @@ -4,9 +4,16 @@ using System; namespace Qiniu.Storage.Tests { + /// + /// PutPolicyTests + /// [TestFixture] public class PutPolicyTests : TestEnv { + /// + /// CreateUptokenTest + /// + /// void [Test] public void CreateUptokenTest() { @@ -69,7 +76,6 @@ public void CreateUptokenTest() putPolicy.PersistentNotifyUrl = "http://api.example.com/qiniu/pfop/notify"; upToken = Auth.CreateUploadToken(mac, putPolicy.ToJsonString()); Console.WriteLine(upToken); - } - + } } } \ No newline at end of file diff --git a/src/QiniuTests/Storage/ResumableUploaderTests.cs b/src/QiniuTests/Storage/ResumableUploaderTests.cs index 8fbe7683..07de9d6f 100644 --- a/src/QiniuTests/Storage/ResumableUploaderTests.cs +++ b/src/QiniuTests/Storage/ResumableUploaderTests.cs @@ -6,9 +6,16 @@ namespace Qiniu.Storage.Tests { + /// + /// ResumableUploaderTests + /// [TestFixture] public class ResumableUploaderTests : TestEnv { + /// + /// UploadFileTest + /// + /// void [Test] public void UploadFileTest() { @@ -34,7 +41,10 @@ public void UploadFileTest() Console.WriteLine("chunk upload result: " + result.ToString()); Assert.AreEqual((int)HttpCode.OK, result.Code); } - + /// + /// ResumeUploadFileTest + /// + /// void [Test] public void ResumeUploadFileTest() { diff --git a/src/QiniuTests/Storage/TokenTests.cs b/src/QiniuTests/Storage/TokenTests.cs new file mode 100644 index 00000000..84f05e2c --- /dev/null +++ b/src/QiniuTests/Storage/TokenTests.cs @@ -0,0 +1,53 @@ +using System; +using System.Net; +using Qiniu.Util; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.IO; + +namespace QiniuTests.Storage +{ + class tokensTest + { + public static void getQiniuToken() + { + // input accessKey secretKey + string ak = ""; + string sk = ""; + // input url + string strUrl = ""; + // input method(POST/GET) + string method = ""; + // input byte[] body + JObject jsonBody = new JObject(); + string jsonobj = JsonConvert.SerializeObject(jsonBody); + byte[] body = jsonobj.ToString().GetBytes("UTF-8"); + //input contentType + string contentType = ""; + + Mac mac = new Mac(ak, sk); + Auth auth = new Auth(mac); + string qiniuToken = Auth.CreateQiniuToken(mac,strUrl, method, body, contentType); + Console.WriteLine(qiniuToken); + } + public static void getQboxToken() + { + // input accessKey secretKey + string ak = ""; + string sk = ""; + // input url + string strUrl = ""; + //input body + JObject jsonBody = new JObject(); + string jsonobj = JsonConvert.SerializeObject(jsonBody); + byte[] body = jsonobj.ToString().GetBytes("UTF-8"); + + Mac mac = new Mac(ak, sk); + Auth auth = new Auth(mac); + string qboxToken = Auth.CreateManageToken(mac,strUrl,body); + Console.WriteLine(qboxToken); + } + } +} + + diff --git a/src/QiniuTests/TestEnv.cs b/src/QiniuTests/TestEnv.cs index c0a2418c..eb1ab48c 100644 --- a/src/QiniuTests/TestEnv.cs +++ b/src/QiniuTests/TestEnv.cs @@ -1,13 +1,39 @@ namespace Qiniu.Tests { + /// + /// TestEnv + /// public class TestEnv { + /// + /// AccessKey + /// public string AccessKey; + /// + /// SecretKey + /// public string SecretKey; + /// + /// Bucket + /// public string Bucket; + /// + /// Domain + /// public string Domain; + /// + /// LocalFile + /// public string LocalFile; + /// + /// Region + /// + public string Region; + /// + /// TestEnv + /// + /// void public TestEnv() { string isTravisTest = System.Environment.GetEnvironmentVariable("isTravisTest"); @@ -23,12 +49,11 @@ public TestEnv() { this.AccessKey = ""; this.SecretKey = ""; - this.Bucket = "csharpsdk"; + this.Bucket = ""; this.Domain = "csharpsdk.qiniudn.com"; this.LocalFile = "E:\\VSProjects\\csharp-sdk\\tools\\files\\test.jpg"; + this.Region = ""; } } - - } } diff --git a/src/QiniuTests/packages.config b/src/QiniuTests/packages.config deleted file mode 100644 index f1240129..00000000 --- a/src/QiniuTests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file