├── tools
├── files
│ └── test.jpg
└── net40
│ ├── Newtonsoft.Json.dll
│ └── nunit.framework.dll
├── src
├── QiniuTests
│ ├── packages.config
│ ├── Util
│ │ ├── StringHelper.cs
│ │ ├── Auth.cs
│ │ └── Signature.cs
│ ├── TestEnv.cs
│ ├── Storage
│ │ ├── ConfigTests.cs
│ │ ├── DownloadManagerTests.cs
│ │ ├── UploadUtilTests.cs
│ │ ├── ZoneHelperTests.cs
│ │ └── OperationManagerTests.cs
│ ├── Http
│ │ ├── HttpResult.cs
│ │ ├── HttpManager.cs
│ │ ├── Middleware.cs
│ │ └── HttpRequestOptions.cs
│ └── QiniuTests.csproj
├── Qiniu
│ ├── packages.config
│ ├── Storage
│ │ ├── UploadProgressHandler.cs
│ │ ├── QiniuException.cs
│ │ ├── UploadController.cs
│ │ ├── FetchInfo.cs
│ │ ├── UploadUtil.cs
│ │ ├── BucketInfo.cs
│ │ ├── ListInfo.cs
│ │ ├── PutExtra.cs
│ │ ├── ResumeContext.cs
│ │ ├── ResumeBlocker.cs
│ │ ├── ZoneInfo.cs
│ │ ├── ResumeInfo.cs
│ │ ├── PrefopResult.cs
│ │ ├── FetchResult.cs
│ │ ├── ListItem.cs
│ │ ├── StatResult.cs
│ │ ├── BucketsResult.cs
│ │ ├── DomainsResult.cs
│ │ ├── PfopResult.cs
│ │ ├── BucketResult.cs
│ │ ├── ResumeHelper.cs
│ │ ├── ChunkUnit.cs
│ │ ├── ListResult.cs
│ │ ├── BatchResult.cs
│ │ ├── UploadManager.cs
│ │ ├── PfopInfo.cs
│ │ ├── FileInfo.cs
│ │ ├── Zone.cs
│ │ ├── DownloadManager.cs
│ │ ├── BatchInfo.cs
│ │ ├── ZoneHelper.cs
│ │ └── PutPolicy.cs
│ ├── Properties
│ │ └── PublishProfiles
│ │ │ └── FolderProfile.pubxml
│ ├── Util
│ │ ├── Mac.cs
│ │ ├── UpToken.cs
│ │ ├── Base64.cs
│ │ ├── Hashing.cs
│ │ ├── UnixTimestamp.cs
│ │ ├── QETag.cs
│ │ ├── ETag.cs
│ │ ├── UserEnv.cs
│ │ ├── UrlHelper.cs
│ │ ├── CRC32.cs
│ │ └── StringHelper.cs
│ ├── Http
│ │ ├── ContentType.cs
│ │ ├── Middleware.cs
│ │ ├── UrlHelper.cs
│ │ ├── HttpHelper.cs
│ │ ├── HttpCode.cs
│ │ └── HttpResult.cs
│ ├── QiniuCSharpSDK.cs
│ ├── Qiniu.csproj
│ └── CDN
│ │ ├── PrefetchInfo.cs
│ │ ├── FluxRequest.cs
│ │ ├── BandwidthRequest.cs
│ │ ├── PrefetchRequest.cs
│ │ ├── FluxInfo.cs
│ │ ├── BandwidthInfo.cs
│ │ ├── RefreshInfo.cs
│ │ ├── LogListInfo.cs
│ │ ├── LogListRequest.cs
│ │ ├── PrefetchResult.cs
│ │ ├── FluxResult.cs
│ │ ├── BandwidthResult.cs
│ │ ├── RefreshRequest.cs
│ │ ├── LogListResult.cs
│ │ └── RefreshResult.cs
└── Qiniu.sln
├── Makefile
├── .gitignore
├── README.md
├── .github
└── workflows
│ ├── version-check.yml
│ └── test-ci.yml
└── LICENSE
/tools/files/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiniu/csharp-sdk/HEAD/tools/files/test.jpg
--------------------------------------------------------------------------------
/tools/net40/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiniu/csharp-sdk/HEAD/tools/net40/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/tools/net40/nunit.framework.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiniu/csharp-sdk/HEAD/tools/net40/nunit.framework.dll
--------------------------------------------------------------------------------
/src/QiniuTests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/Qiniu/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: build-and-test build test publish
2 |
3 | build-and-test: build test
4 | build:
5 | dotnet build src/Qiniu/Qiniu.csproj
6 | test:
7 | dotnet test src/QiniuTests/QiniuTests.csproj
8 | publish:
9 | dotnet publish src/Qiniu/Qiniu.csproj -c Release -f netstandard2.0
10 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/UploadProgressHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Qiniu.Storage
2 | {
3 | ///
4 | /// 分片上传进度处理
5 | ///
6 | /// 已上传的字节数
7 | /// 文件总字节数
8 | public delegate void UploadProgressHandler(long uploadedBytes, long totalBytes);
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | #ignore thumbnails created by windows
3 | Thumbs.db
4 | #Ignore files build by Visual Studio
5 | *.obj
6 | *.exe
7 | *.pdb
8 | *.user
9 | *.aps
10 | *.pch
11 | *.vspscc
12 | *_i.c
13 | *_p.c
14 | *.ncb
15 | *.suo
16 | *.tlb
17 | *.tlh
18 | *.bak
19 | *.cache
20 | *.ilk
21 | *.log
22 | #[Bb]in
23 | [Dd]ebug*/
24 | *.lib
25 | *.sbr
26 | obj/
27 | [Rr]elease*/
28 | _ReSharper*/
29 | [Tt]est[Rr]esult*
30 | /src/packages
31 | /packages
32 | /bin
33 | .vs
34 | /src/QiniuTests/TestEnv.cs
35 | /src/QiniuTests/coverage.json
36 |
--------------------------------------------------------------------------------
/src/QiniuTests/Util/StringHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using NUnit.Framework;
3 |
4 | using Qiniu.Util;
5 | using Qiniu.Tests;
6 |
7 | namespace QiniuTests.Util
8 | {
9 | [TestFixture]
10 | public class StringHelperTests : TestEnv
11 | {
12 | [TestCaseSource(typeof(CanonicalMimeHeaderKeyDataClass), nameof(CanonicalMimeHeaderKeyDataClass.TestCases))]
13 | public string CanonicalMimeHeaderKeyTest(string fieldName)
14 | {
15 | return StringHelper.CanonicalMimeHeaderKey(fieldName);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Qiniu (Cloud) C# SDK
2 |
3 | ## 使用
4 |
5 | * 参考文档:[七牛云存储 C# SDK 使用指南](https://developer.qiniu.com/kodo/sdk/1237/csharp)
6 | * 可以参考我们为大家精心准备的使用 [实例](https://github.com/qiniu/csharp-sdk/tree/master/src/QiniuTests)
7 |
8 |
9 | ## 贡献代码
10 |
11 | 1. Fork
12 |
13 | 2. 创建您的特性分支 git checkout -b my-new-feature
14 |
15 | 3. 提交您的改动 git commit -am 'Added some feature'
16 |
17 | 4. 将您的修改记录提交到远程 git 仓库 git push origin my-new-feature
18 |
19 | 5. 然后到 github 网站的该 git 远程仓库的 my-new-feature 分支下发起 Pull Request
20 |
21 |
22 | ## 许可证
23 |
24 | Copyright (c) 2017 [qiniu.com](www.qiniu.com)
25 |
--------------------------------------------------------------------------------
/src/Qiniu/Properties/PublishProfiles/FolderProfile.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | Release
9 | Any CPU
10 | netstandard2.0
11 | C:\Users\zhour\source\repos\csharp-sdk\bin\netstandard2.0
12 |
13 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/QiniuException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Qiniu.Http;
3 |
4 | namespace Qiniu.Storage
5 | {
6 | class QiniuException :Exception
7 | {
8 | public string message;
9 | public HttpResult HttpResult;
10 | public QiniuException(HttpResult httpResult, string message)
11 | {
12 | this.HttpResult = httpResult == null ? new HttpResult() : httpResult;
13 | this.message = message;
14 | }
15 |
16 | public override string Message
17 | {
18 | get
19 | {
20 | return this.message;
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/QiniuTests/TestEnv.cs:
--------------------------------------------------------------------------------
1 | namespace Qiniu.Tests
2 | {
3 | public class TestEnv
4 | {
5 | public string AccessKey;
6 | public string SecretKey;
7 | public string Bucket;
8 | public string Domain;
9 |
10 | public TestEnv()
11 | {
12 | this.AccessKey = System.Environment.GetEnvironmentVariable("QINIU_ACCESS_KEY");
13 | this.SecretKey = System.Environment.GetEnvironmentVariable("QINIU_SECRET_KEY");
14 | this.Bucket = System.Environment.GetEnvironmentVariable("QINIU_TEST_BUCKET");
15 | this.Domain = System.Environment.GetEnvironmentVariable("QINIU_TEST_DOMAIN"); }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Qiniu/Storage/UploadController.cs:
--------------------------------------------------------------------------------
1 | namespace Qiniu.Storage
2 | {
3 | ///
4 | /// 上传任务的状态
5 | ///
6 | public enum UploadControllerAction
7 | {
8 | ///
9 | /// 任务状态:激活
10 | ///
11 | Activated,
12 |
13 | ///
14 | /// 任务状态:暂停
15 | ///
16 | Suspended,
17 |
18 | ///
19 | /// 任务状态:退出
20 | ///
21 | Aborted
22 | };
23 |
24 | ///
25 | /// 上传任务的控制函数
26 | ///
27 | ///
28 | public delegate UploadControllerAction UploadController();
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/version-check.yml:
--------------------------------------------------------------------------------
1 | name: C# SDK Version Check
2 | on:
3 | push:
4 | tags:
5 | - 'v[0-9]+.[0-9]+.[0-9]+'
6 | jobs:
7 | linux:
8 | name: Version Check
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout code
12 | uses: actions/checkout@v2
13 | - name: Set env
14 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV
15 | - name: Check
16 | run: |
17 | set -e
18 | grep -qF "${RELEASE_VERSION}" src/Qiniu/Qiniu.csproj
19 | grep -qF "v${RELEASE_VERSION}" CHANGELOG.md
20 | grep -qF "public const string VERSION = \"${RELEASE_VERSION}\";" src/Qiniu/QiniuCSharpSDK.cs
21 |
--------------------------------------------------------------------------------
/src/Qiniu/Util/Mac.cs:
--------------------------------------------------------------------------------
1 | namespace Qiniu.Util
2 | {
3 | ///
4 | /// 账户访问控制(密钥)
5 | ///
6 | public class Mac
7 | {
8 | ///
9 | /// 密钥-AccessKey
10 | ///
11 | public string AccessKey { set; get; }
12 |
13 | ///
14 | /// 密钥-SecretKey
15 | ///
16 | public string SecretKey { set; get; }
17 |
18 | ///
19 | /// 初始化密钥AK/SK
20 | ///
21 | /// AccessKey
22 | /// SecretKey
23 | public Mac(string accessKey, string secretKey)
24 | {
25 | this.AccessKey = accessKey;
26 | this.SecretKey = secretKey;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/Qiniu/Storage/FetchInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | namespace Qiniu.Storage
3 | {
4 | ///
5 | /// 资源抓取返回的内容
6 | ///
7 | public class FetchInfo
8 | {
9 | ///
10 | /// 文件名
11 | ///
12 | [JsonProperty("key")]
13 | public string Key { set; get; }
14 |
15 | ///
16 | /// 文件大小(字节)
17 | ///
18 | [JsonProperty("fsize")]
19 | public long Fsize { set; get; }
20 |
21 | ///
22 | /// 文件hash(ETAG)
23 | ///
24 | [JsonProperty("hash")]
25 | public string Hash { set; get; }
26 |
27 | ///
28 | /// 文件MIME类型
29 | ///
30 | [JsonProperty("mimeType")]
31 | public string MimeType { set; get; }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.github/workflows/test-ci.yml:
--------------------------------------------------------------------------------
1 | name: CSHARP CI
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - '**.md'
7 |
8 | jobs:
9 | build:
10 | strategy:
11 | fail-fast: false
12 | max-parallel: 1
13 |
14 | runs-on: windows-latest
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v2
18 |
19 | - name: Setup
20 | uses: actions/setup-dotnet@v1
21 | with:
22 | dotnet-version: 2.1.502
23 |
24 | - name: Build
25 | run: dotnet build src/Qiniu/Qiniu.csproj
26 |
27 | - name: Test
28 | run: dotnet test -v n src/QiniuTests/QiniuTests.csproj
29 |
30 | env:
31 | QINIU_ACCESS_KEY: ${{ secrets.QINIU_ACCESS_KEY }}
32 | QINIU_SECRET_KEY: ${{ secrets.QINIU_SECRET_KEY }}
33 | QINIU_TEST_BUCKET: ${{ secrets.QINIU_TEST_BUCKET }}
34 | QINIU_TEST_DOMAIN: ${{ secrets.QINIU_TEST_DOMAIN }}
35 |
--------------------------------------------------------------------------------
/src/QiniuTests/Util/Auth.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Specialized;
3 | using NUnit.Framework;
4 | using Qiniu.Util;
5 |
6 | namespace QiniuTests.Util
7 | {
8 | [TestFixture]
9 | public class AuthTests
10 | {
11 | private static Mac mac = new Mac("ak", "sk");
12 | private static Auth auth = new Auth(mac);
13 |
14 | [TestCaseSource(typeof(SignatureV2DataClass), nameof(SignatureV2DataClass.TestCases))]
15 | public string CreateManageTokenV2Test(string method, string url, StringDictionary headers,
16 | string body)
17 | {
18 | return auth.CreateManageTokenV2(method, url, headers, body);
19 | }
20 |
21 | [Test]
22 | public void CreateManageTokenV2Test()
23 | {
24 | string actual = auth.CreateManageTokenV2("GET", "http://rs.qbox.me");
25 | Assert.AreEqual("Qiniu ak:bgfeAqx6xXMIXA232e8ocxfhINc=", actual);
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/QiniuTests/Storage/ConfigTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Qiniu.Tests;
3 |
4 | namespace Qiniu.Storage.Tests
5 | {
6 | [TestFixture]
7 | public class ConfigTests : TestEnv
8 | {
9 | [Test]
10 | public void UcHostTest()
11 | {
12 | Config config = new Config();
13 | string ucHost = config.UcHost();
14 | Assert.AreEqual("http://uc.qiniuapi.com", ucHost);
15 | config.SetUcHost("uc.example.com");
16 | ucHost = config.UcHost();
17 | Assert.AreEqual("http://uc.example.com", ucHost);
18 |
19 | config = new Config();
20 | config.UseHttps = true;
21 | ucHost = config.UcHost();
22 | Assert.AreEqual("https://uc.qiniuapi.com", ucHost);
23 | config.SetUcHost("uc.example.com");
24 | ucHost = config.UcHost();
25 | Assert.AreEqual("https://uc.example.com", ucHost);
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Qiniu/Http/ContentType.cs:
--------------------------------------------------------------------------------
1 | namespace Qiniu.Http
2 | {
3 | ///
4 | /// HTTP 内容类型(Content-Type)
5 | ///
6 | public class ContentType
7 | {
8 | ///
9 | /// 资源类型:普通文本
10 | ///
11 | public static string TEXT_PLAIN = "text/plain";
12 |
13 | ///
14 | /// 资源类型:JSON字符串
15 | ///
16 | public static string APPLICATION_JSON = "application/json";
17 |
18 | ///
19 | /// 资源类型:未知类型(数据流)
20 | ///
21 | public static string APPLICATION_OCTET_STREAM = "application/octet-stream";
22 |
23 | ///
24 | /// 资源类型:表单数据(键值对)
25 | ///
26 | public static string WWW_FORM_URLENC = "application/x-www-form-urlencoded";
27 |
28 | ///
29 | /// 资源类型:多分部数据
30 | ///
31 | public static string MULTIPART_FORM_DATA = "multipart/form-data";
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/QiniuTests/Storage/DownloadManagerTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Qiniu.Storage;
3 | using System;
4 | using Qiniu.Util;
5 | using Qiniu.Tests;
6 |
7 | namespace Qiniu.Storage.Tests
8 | {
9 | [TestFixture]
10 | public class DownloadManagerTests : TestEnv
11 | {
12 | [Test]
13 | public void CreatePrivateUrlTest()
14 | {
15 | Mac mac = new Mac(AccessKey, SecretKey);
16 | string domain = "http://if-pri.qiniudn.com";
17 | string key = "hello/world/七牛/test.png";
18 | string privateUrl = DownloadManager.CreatePrivateUrl(mac, domain, key, 3600);
19 | Console.WriteLine(privateUrl);
20 | }
21 |
22 | [Test]
23 | public void CreatePublishUrlTest()
24 | {
25 | string domain = "http://if-pbl.qiniudn.com";
26 | string key = "hello/world/七牛/test.png";
27 | string publicUrl = DownloadManager.CreatePublishUrl(domain, key);
28 | Console.WriteLine(publicUrl);
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 qiniu.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/Qiniu/QiniuCSharpSDK.cs:
--------------------------------------------------------------------------------
1 | ///
2 | /// Qiniu (Cloud) C# SDK for .NET Framework 2.0+/Core/UWP
3 | /// Modules in this SDK:
4 | /// "Storage" 存储相关功能,上传,下载,数据处理,资源管理
5 | /// "CDN", Fusion CDN, 融合CDN加速;
6 | /// "Util", Utilities such as MD5 hashing, 实用工具(如MD5哈希计算等);
7 | /// "Http", HTTP Request Manager, HTTP请求管理器
8 | ///
9 | public class QiniuCSharpSDK
10 | {
11 | ///
12 | /// SDK名称
13 | ///
14 | public const string ALIAS = "QiniuCSharpSDK";
15 |
16 | ///
17 | /// 目标框架
18 | ///
19 | #if Net20
20 | public const string RTFX = "NET20";
21 | #elif Net35
22 | public const string RTFX = "NET35";
23 | #elif Net40
24 | public const string RTFX = "NET40";
25 | #elif Net45
26 | public const string RTFX = "NET45";
27 | #elif Net46
28 | public const string RTFX = "NET46";
29 | #elif NetCore
30 | public const string RTFX = "NETCore";
31 | #elif WINDOWS_UWP
32 | public const string RTFX = "UWP";
33 | #else
34 | public const string RTFX = "UNKNOWN";
35 | #endif
36 |
37 | ///
38 | /// SDK版本号
39 | ///
40 | public const string VERSION = "8.7.0";
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/UploadUtil.cs:
--------------------------------------------------------------------------------
1 | using Qiniu.Http;
2 |
3 | namespace Qiniu.Storage
4 | {
5 | public static class UploadUtil
6 | {
7 | public static bool ShouldRetry(int code, int refCode)
8 | {
9 | if (code == (int) HttpCode.OK)
10 | {
11 | return false;
12 | }
13 |
14 | // allow list
15 | if (
16 | refCode == (int)HttpCode.USER_UNDEF ||
17 | refCode == (int)HttpCode.USER_NEED_RETRY
18 | )
19 | {
20 | return true;
21 | }
22 |
23 | // block list
24 | int codeSeries = code / 100;
25 |
26 | if (codeSeries == 4 && code != (int)HttpCode.CRC32_CHECK_FAILEd)
27 | {
28 | return false;
29 | }
30 |
31 | if (
32 | code == (int)HttpCode.FILE_NOT_EXIST ||
33 | code == (int)HttpCode.FILE_EXISTS ||
34 | code == (int)HttpCode.CALLBACK_FAILED
35 | )
36 | {
37 | return false;
38 | }
39 |
40 | // others need retry
41 | return true;
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/src/Qiniu/Storage/BucketInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Qiniu.Storage
2 | {
3 | ///
4 | /// bucket info
5 | ///
6 | public class BucketInfo
7 | {
8 | ///
9 | /// bucket name
10 | ///
11 | public string tbl { get; set; }
12 |
13 | ///
14 | /// itbl
15 | ///
16 | public long itbl { get; set; }
17 |
18 | ///
19 | /// deprecated
20 | ///
21 | public string phy {get;set;}
22 |
23 | ///
24 | /// id
25 | ///
26 | public long uid { get; set; }
27 |
28 | ///
29 | /// zone
30 | ///
31 | public string zone { get; set; }
32 |
33 | ///
34 | /// region
35 | ///
36 | public string region { get; set; }
37 |
38 | ///
39 | /// isGlobal
40 | ///
41 | public bool global { get; set; }
42 |
43 | ///
44 | /// isLineStorage
45 | ///
46 | public bool line { get; set; }
47 |
48 | ///
49 | /// creationTime
50 | ///
51 | public long ctime { get; set; }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/ListInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Newtonsoft.Json;
3 | namespace Qiniu.Storage
4 | {
5 | ///
6 | /// 获取空间文件(list操作)
7 | ///
8 | /// 返回JSON字符串
9 | ///
10 | /// {
11 | /// "marker":"MARKER",
12 | /// "items":
13 | /// [
14 | /// {
15 | /// "key":"KEY",
16 | /// "hash":"HASH",
17 | /// "fsize":FSIZE,
18 | /// "mimeType":"MIME_TYPE",
19 | /// "putTime":PUT_TIME,
20 | /// "type":FILE_TYPE
21 | /// },
22 | /// {
23 | /// ...
24 | /// }
25 | /// ],
26 | /// "CmmonPrefixes":"COMMON_PREFIXES"
27 | /// }
28 | ///
29 | ///
30 | public class ListInfo
31 | {
32 | ///
33 | /// marker标记
34 | ///
35 | [JsonProperty("marker", NullValueHandling = NullValueHandling.Ignore)]
36 | public string Marker { get; set; }
37 |
38 | ///
39 | /// 文件列表
40 | ///
41 | [JsonProperty("items", NullValueHandling = NullValueHandling.Ignore)]
42 | public List Items { get; set; }
43 |
44 | ///
45 | /// 公共前缀
46 | ///
47 | [JsonProperty("commonPrefixes", NullValueHandling = NullValueHandling.Ignore)]
48 | public List CommonPrefixes { get; set; }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/QiniuTests/Util/Signature.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Specialized;
2 | using System.Text;
3 | using NUnit.Framework;
4 | using Qiniu.Tests;
5 | using Qiniu.Util;
6 |
7 | namespace QiniuTests.Util
8 | {
9 | [TestFixture]
10 | public class SignatureTest : TestEnv
11 | {
12 | static Mac mac = new Mac("ak", "sk");
13 | static Signature sign = new Signature(mac);
14 |
15 | [TestCaseSource(typeof(SignatureV2DataClass), nameof(SignatureV2DataClass.TestCases))]
16 | public string SignatureV2Test(string method, string url, StringDictionary headers, string body)
17 | {
18 | return string.Format("Qiniu {0}", sign.SignRequestV2(method, url, headers, body));
19 | }
20 |
21 | [TestCaseSource(typeof(SignatureV2DataClass), nameof(SignatureV2DataClass.TestCases))]
22 | public string SignatureV2ByBytesTest(string method, string url, StringDictionary headers, string body)
23 | {
24 | return string.Format("Qiniu {0}", sign.SignRequestV2(method, url, headers, Encoding.UTF8.GetBytes(body)));
25 | }
26 |
27 | [TestCaseSource(typeof(VerifyRequestDataClass), nameof(VerifyRequestDataClass.TestCases))]
28 | public bool VerifyRequestTest(string method, string url, StringDictionary headers, string body)
29 | {
30 | Mac mac = new Mac("abcdefghklmnopq", "1234567890");
31 | Signature mockSign = new Signature(mac);
32 | return mockSign.VerifyRequest(method, url, headers, body);
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/Qiniu/Storage/PutExtra.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Qiniu.Storage
4 | {
5 | ///
6 | /// 文件上传的额外可选设置
7 | ///
8 | public class PutExtra
9 | {
10 | ///
11 | /// 设置文件断点续传进度记录文件
12 | ///
13 | public string ResumeRecordFile { set; get; }
14 | ///
15 | /// 上传可选参数字典,参数名次以 x: 开头
16 | ///
17 | public Dictionary Params;
18 | ///
19 | /// 指定文件的MimeType
20 | ///
21 | public string MimeType { set; get; }
22 | ///
23 | /// 设置文件上传进度处理器
24 | ///
25 | public UploadProgressHandler ProgressHandler { set; get; }
26 | ///
27 | /// 设置文件上传的状态控制器
28 | ///
29 | public UploadController UploadController { set; get; }
30 |
31 | ///
32 | /// 最大重试次数
33 | ///
34 | /// 默认值应与 一致
35 | public int MaxRetryTimes { set; get; } = 3;
36 |
37 | ///
38 | /// 块并发上传的线程数量
39 | ///
40 | public int BlockUploadThreads { set; get; }
41 |
42 | ///
43 | /// 分片上传版本 目前支持v1/v2版本 默认v1
44 | ///
45 | public string Version = "v1";
46 |
47 | ///
48 | /// 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB
49 | ///
50 | public int PartSize = 4 * 1024 * 1024;
51 | }
52 | }
--------------------------------------------------------------------------------
/src/Qiniu/Storage/ResumeContext.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 |
4 | namespace Qiniu.Storage
5 | {
6 | ///
7 | /// 分片上传的上下文信息
8 | ///
9 | public class ResumeContext
10 | {
11 | ///
12 | /// 上下文信息
13 | ///
14 | [JsonProperty("ctx")]
15 | public string Ctx { get; set; }
16 |
17 | ///
18 | /// 校验和
19 | ///
20 | [JsonProperty("checksum")]
21 | public string Checksum { get; set; }
22 |
23 | ///
24 | /// crc32校验值
25 | ///
26 | [JsonProperty("crc32")]
27 | public uint Crc32 { get; set; }
28 |
29 | ///
30 | /// 文件偏移位置
31 | ///
32 | [JsonProperty("offset")]
33 | public long Offset { get; set; }
34 |
35 | ///
36 | /// 上传目的host
37 | ///
38 | [JsonProperty("host")]
39 | public string Host { get; set; }
40 |
41 | ///
42 | /// ctx失效时刻
43 | ///
44 | [JsonProperty("expired_at")]
45 | public long ExpiredAt { get; set; }
46 |
47 | ///
48 | /// 新版分片上传上下文etag
49 | ///
50 | [JsonProperty("etag")]
51 | public Dictionary Etag { get; set; }
52 |
53 | ///
54 | /// 新版分片上传md5校验值
55 | ///
56 | [JsonProperty("md5")]
57 | public string Md5 { get; set; }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/ResumeBlocker.cs:
--------------------------------------------------------------------------------
1 | using Qiniu.Http;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 |
5 | namespace Qiniu.Storage
6 | {
7 | class ResumeBlocker
8 | {
9 | public ManualResetEvent DoneEvent { set; get; }
10 | public byte[] BlockBuffer { set; get; }
11 | public long BlockIndex { set; get; }
12 | public string UploadToken { set; get; }
13 | public PutExtra PutExtra { set; get; }
14 | public ResumeInfo ResumeInfo { set; get; }
15 | public Dictionary BlockMakeResults;
16 | public object ProgressLock { set; get; }
17 | public Dictionary UploadedBytesDict { set; get; }
18 | public long FileSize { set; get; }
19 | public string encodedObjectName { set; get; } // 仅用于分片上传 V2
20 |
21 | public ResumeBlocker(ManualResetEvent doneEvent, byte[] blockBuffer, long blockIndex, string uploadToken,
22 | PutExtra putExtra, ResumeInfo resumeInfo, Dictionary blockMakeResults,
23 | object progressLock, Dictionary uploadedBytesDict, long fileSize, string encodedObjectName)
24 | {
25 | this.DoneEvent = doneEvent;
26 | this.BlockBuffer = blockBuffer;
27 | this.BlockIndex = blockIndex;
28 | this.UploadToken = uploadToken;
29 | this.PutExtra = putExtra;
30 | this.ResumeInfo = resumeInfo;
31 | this.BlockMakeResults = blockMakeResults;
32 | this.ProgressLock = progressLock;
33 | this.UploadedBytesDict = uploadedBytesDict;
34 | this.FileSize = fileSize;
35 | this.encodedObjectName = encodedObjectName;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Qiniu/Qiniu.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Release
5 | AnyCPU
6 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}
7 | Library
8 | Properties
9 | Qiniu
10 | Qiniu
11 | netstandard2.0
12 | 512
13 |
14 |
15 |
16 | false
17 |
18 |
19 | QiniuCSharpSDK.snk
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Qiniu
31 | 8.7.0
32 | Rong Zhou, Qiniu SDK
33 | Shanghai Qiniu Information Technology Co., Ltd.
34 | Qiniu Resource (Cloud) Storage SDK for C#
35 |
36 |
43 |
44 |
--------------------------------------------------------------------------------
/src/QiniuTests/Storage/UploadUtilTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System.Collections;
3 | using Qiniu.Tests;
4 |
5 | namespace Qiniu.Storage.Tests
6 | {
7 | public class UploadUtilTestCases
8 | {
9 | public static IEnumerable ShouldRetryTestCases
10 | {
11 | get
12 | {
13 | // 200 always false
14 | yield return new TestCaseData(200, 200).Returns(false);
15 |
16 | // some refCode should retry
17 | yield return new TestCaseData(0, 3).Returns(true);
18 | yield return new TestCaseData(0, 0).Returns(true);
19 |
20 | // 4xx shouldn't retry but 406
21 | yield return new TestCaseData(406, 406).Returns(true);
22 | yield return new TestCaseData(400, 400).Returns(false);
23 |
24 | // some code shouldn't retry
25 | yield return new TestCaseData(612, 612).Returns(false);
26 | yield return new TestCaseData(614, 614).Returns(false);
27 | yield return new TestCaseData(579, 579).Returns(false);
28 |
29 | // any others should retry
30 | yield return new TestCaseData(500, 500).Returns(true);
31 | yield return new TestCaseData(502, 502).Returns(true);
32 | }
33 | }
34 | }
35 |
36 | [TestFixture]
37 | public class UploadUtilTests : TestEnv
38 | {
39 | [TestCaseSource(typeof(UploadUtilTestCases), nameof(UploadUtilTestCases.ShouldRetryTestCases))]
40 | public bool ShouldRetryTest(int code, int refCode)
41 | {
42 | return UploadUtil.ShouldRetry(code, refCode);
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/src/Qiniu/CDN/PrefetchInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Qiniu.CDN
4 | {
5 | ///
6 | /// 文件预取-消息内容结构
7 | ///
8 | /// 在请求成功时 code 为 200,requestId、quotaDay、surplusDay 才会有有效值,否则为空。
9 | /// 在请求失败时 code 为非 200,error 中包含描述信息。
10 | ///
11 | /// 以下是一个返回结果示例
12 | ///
13 | /// {
14 | /// "code":200,
15 | /// "error":"success",
16 | /// "requestId":"577471ace3ab3a030c058972",
17 | /// "invalidUrls":null,
18 | /// "quotaDay":100,
19 | /// "surplusDay":99
20 | /// }
21 | ///
22 | ///
23 | public class PrefetchInfo
24 | {
25 | ///
26 | /// 代码 含义 说明
27 | /// /// 200 success 成功(OK)
28 | /// 400031 invalid url 请求中存在无效的 url,请确保提交的 url 格式正确
29 | /// 400032 invalid host 请求中存在无效的域名,请确保域名格式正确
30 | /// 400033 prefetch url limit error 请求次数超出当日预取限额
31 | /// 400036 invalid request id 无效的请求 id
32 | /// 400037 url has existed url 正在预取中
33 | /// 500000 internal error 服务端内部错误,请联系技术支持
34 | ///
35 | public int Code { get; set; }
36 |
37 | ///
38 | /// 错误消息(状态码非OK时)
39 | ///
40 | public string Error { get; set; }
41 |
42 | ///
43 | /// 请求ID(可用于反馈排查)
44 | ///
45 | public string RequestId { get; set; }
46 |
47 | ///
48 | /// 非法URL
49 | ///
50 | public List InvalidUrls { get; set; }
51 |
52 | ///
53 | /// 当日限额
54 | ///
55 | public int QuotaDay { get; set; }
56 |
57 | ///
58 | /// 当日剩余额度
59 | ///
60 | public int SurplusDay { get; set; }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/ZoneInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace Qiniu.Storage
4 | {
5 | ///
6 | /// 从uc.qbox.me返回的消息
7 | ///
8 | internal class ZoneInfo
9 | {
10 | [JsonProperty("hosts")]
11 | public ZoneHost[] Hosts { get; set; }
12 | }
13 |
14 | internal class ZoneHost
15 | {
16 | ///
17 | /// 过期时间,单位:秒
18 | ///
19 | [JsonProperty("ttl")]
20 | public int Ttl { get; set; }
21 |
22 | [JsonProperty("region")]
23 | public string Region { get; set; }
24 |
25 | [JsonProperty("io")]
26 | public ServiceDomains Io { get; set; }
27 |
28 | [JsonProperty("io_src")]
29 | public ServiceDomains IoSrc { get; set; }
30 |
31 | [JsonProperty("up")]
32 | public ServiceDomains Up { get; set; }
33 |
34 | [JsonProperty("api")]
35 | public ServiceDomains Api { get; set; }
36 |
37 | [JsonProperty("rs")]
38 | public ServiceDomains Rs { get; set; }
39 |
40 | [JsonProperty("rsf")]
41 | public ServiceDomains Rsf { get; set; }
42 |
43 | [JsonProperty("s3")]
44 | public ServiceDomains S3 { get; set; }
45 |
46 | [JsonProperty("uc")]
47 | public ServiceDomains Uc { get; set; }
48 |
49 | internal class ServiceDomains
50 | {
51 | [JsonProperty("domains")]
52 | public string[] Domains { get; set; }
53 |
54 | [JsonProperty("old", NullValueHandling = NullValueHandling.Ignore)]
55 | public string[] Old { get; set; }
56 |
57 | [JsonProperty("region_alias", NullValueHandling = NullValueHandling.Ignore)]
58 | public string RegionAlias { get; set; }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/ResumeInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 | namespace Qiniu.Storage
4 | {
5 | ///
6 | /// 分片上传的记录信息
7 | ///
8 | public class ResumeInfo
9 | {
10 | ///
11 | /// 文件大小
12 | ///
13 | [JsonProperty("fileSize")]
14 | public long FileSize { get; set; }
15 |
16 | ///
17 | /// 文件块总数
18 | ///
19 | [JsonProperty("blockCount")]
20 | public long BlockCount { get; set; }
21 |
22 | ///
23 | /// 上下文信息列表
24 | ///
25 | [JsonProperty("contexts")]
26 | public string[] Contexts { get; set; }
27 |
28 | ///
29 | /// 上下文信息过期列表,与 context 配合使用
30 | ///
31 | [JsonProperty("contextsExpiredAt")]
32 | public long[] ContextsExpiredAt { get; set; }
33 |
34 | ///
35 | /// Ctx过期时间戳(单位秒)
36 | ///
37 | [JsonProperty("expiredAt")]
38 | public long ExpiredAt { get; set; }
39 |
40 | ///
41 | /// 上传进度信息序列化
42 | ///
43 | ///
44 |
45 | ///
46 | /// 新版分片上下文信息列表
47 | ///
48 | [JsonProperty("etags")]
49 | public Dictionary[] Etags { get; set; }
50 |
51 | ///
52 | /// 新版分片上传id
53 | ///
54 | [JsonProperty("uploadId")]
55 | public string UploadId { get; set; }
56 |
57 | ///
58 | /// 完成上传的字节数
59 | ///
60 | [JsonProperty("uploaded")]
61 | public long Uploaded { get; set; }
62 |
63 | public string ToJsonStr()
64 | {
65 | return JsonConvert.SerializeObject(this);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Qiniu/Util/UpToken.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using Newtonsoft.Json;
4 | using Qiniu.Storage;
5 | namespace Qiniu.Util
6 | {
7 | ///
8 | /// 上传凭证工具类
9 | ///
10 | public class UpToken
11 | {
12 | ///
13 | /// 从上传凭证获取AccessKey
14 | ///
15 | /// 上传凭证
16 | /// AccessKey
17 | public static string GetAccessKeyFromUpToken(string upToken)
18 | {
19 | string accessKey = null;
20 | string[] items = upToken.Split(':');
21 | if (items.Length == 3)
22 | {
23 | accessKey = items[0];
24 | }
25 | return accessKey;
26 | }
27 |
28 | ///
29 | /// 从上传凭证获取Bucket
30 | ///
31 | /// 上传凭证
32 | /// Bucket
33 | public static string GetBucketFromUpToken(string upToken)
34 | {
35 | string bucket = null;
36 | string[] items = upToken.Split(':');
37 | if (items.Length == 3)
38 | {
39 | string encodedPolicy = items[2];
40 | try
41 | {
42 | string policyStr = Encoding.UTF8.GetString(Base64.UrlsafeBase64Decode(encodedPolicy));
43 | PutPolicy putPolicy = JsonConvert.DeserializeObject(policyStr);
44 | string scope = putPolicy.Scope;
45 | string[] scopeItems = scope.Split(':');
46 | if (scopeItems.Length >= 1)
47 | {
48 | bucket = scopeItems[0];
49 | }
50 | }catch(Exception)
51 | {
52 |
53 | }
54 | }
55 | return bucket;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/FluxRequest.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | namespace Qiniu.CDN
3 | {
4 | ///
5 | /// 查询流量-请求
6 | ///
7 | public class FluxRequest
8 | {
9 | ///
10 | /// 起始日期,例如2016-09-01
11 | ///
12 | [JsonProperty("startDate")]
13 | public string StartDate { get; set; }
14 |
15 | ///
16 | /// 结束日期,例如2016-09-10
17 | ///
18 | [JsonProperty("endDate")]
19 | public string EndDate { get; set; }
20 |
21 | ///
22 | /// 时间粒度((取值:5min / hour /day))
23 | ///
24 | [JsonProperty("granularity")]
25 | public string Granularity { get; set; }
26 |
27 | ///
28 | /// 域名列表,以西文半角分号分割
29 | ///
30 | [JsonProperty("domains")]
31 | public string Domains { get; set; }
32 |
33 | ///
34 | /// 初始化(所有成员为空,需要后续赋值)
35 | ///
36 | public FluxRequest()
37 | {
38 | StartDate = "";
39 | EndDate = "";
40 | Granularity = "";
41 | Domains = "";
42 | }
43 |
44 | ///
45 | /// 初始化所有成员
46 | ///
47 | /// 起始日期
48 | /// 结束日期
49 | /// 时间粒度
50 | /// 域名列表
51 | public FluxRequest(string startDate, string endDate, string granularity, string domains)
52 | {
53 | StartDate = startDate;
54 | EndDate = endDate;
55 | Granularity = granularity;
56 | Domains = domains;
57 | }
58 |
59 | ///
60 | /// 转换到JSON字符串
61 | ///
62 | /// 请求内容的JSON字符串
63 | public string ToJsonStr()
64 | {
65 | return JsonConvert.SerializeObject(this);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/BandwidthRequest.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | namespace Qiniu.CDN
3 | {
4 | ///
5 | /// 查询带宽-请求
6 | ///
7 | public class BandwidthRequest
8 | {
9 | ///
10 | /// 起始日期,例如2016-09-01
11 | ///
12 | [JsonProperty("startDate")]
13 | public string StartDate { get; set; }
14 |
15 | ///
16 | /// 结束日期,例如2016-09-10
17 | ///
18 | [JsonProperty("endDate")]
19 | public string EndDate { get; set; }
20 |
21 | ///
22 | /// 时间粒度((取值:5min / hour /day))
23 | ///
24 | [JsonProperty("granularity")]
25 | public string Granularity { get; set; }
26 |
27 | ///
28 | /// 域名列表,以西文半角分号分割
29 | ///
30 | [JsonProperty("domains")]
31 | public string Domains { get; set; }
32 |
33 | ///
34 | /// 初始化(所有成员为空,需要后续赋值)
35 | ///
36 | public BandwidthRequest()
37 | {
38 | StartDate = "";
39 | EndDate = "";
40 | Granularity = "";
41 | Domains = "";
42 | }
43 |
44 | ///
45 | /// 初始化所有成员
46 | ///
47 | /// 起始日期
48 | /// 结束日期
49 | /// 时间粒度
50 | /// 域名列表
51 | public BandwidthRequest(string startDate, string endDate, string granularity, string domains)
52 | {
53 | StartDate = startDate;
54 | EndDate = endDate;
55 | Granularity = granularity;
56 | Domains = domains;
57 | }
58 |
59 | ///
60 | /// 转换到JSON字符串
61 | ///
62 | /// 请求内容的JSON字符串
63 | public string ToJsonStr()
64 | {
65 | return JsonConvert.SerializeObject(this);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/PrefetchRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 | using Newtonsoft.Json;
4 |
5 | namespace Qiniu.CDN
6 | {
7 | ///
8 | /// 文件预取-请求
9 | ///
10 | public class PrefetchRequest
11 | {
12 | ///
13 | /// 要预取的单个url列表,总数不超过100条
14 | /// 单个url,即一个具体的url,例如:http://bar.foo.com/test.zip
15 | /// 注意:
16 | /// 请输入资源 url 完整的绝对路径,由 http:// 或 https:// 开始
17 | /// 资源 url 不支持通配符,例如:不支持 http://www.test.com/abc/*.*
18 | ///
19 | [JsonProperty("urls",NullValueHandling=NullValueHandling.Ignore)]
20 | public List Urls { get; set; }
21 |
22 | ///
23 | /// 初始化(URL列表为空,需要后续赋值)
24 | ///
25 | public PrefetchRequest()
26 | {
27 | Urls = new List();
28 | }
29 |
30 | ///
31 | /// 初始化(URL列表)
32 | ///
33 | /// URL列表
34 | public PrefetchRequest(IList urls)
35 | {
36 | if (urls != null)
37 | {
38 | Urls = new List(urls);
39 | }
40 | else
41 | {
42 | Urls = new List();
43 | }
44 | }
45 |
46 | ///
47 | /// 添加要查询的URL
48 | ///
49 | /// URL列表
50 | public void AddUrls(IList urls)
51 | {
52 | if (urls != null)
53 | {
54 | foreach (string u in urls)
55 | {
56 | if(!Urls.Contains(u))
57 | {
58 | Urls.Add(u);
59 | }
60 | }
61 | }
62 | }
63 |
64 | ///
65 | /// 转换到JSON字符串
66 | ///
67 | /// 请求内容的JSON字符串
68 | public string ToJsonStr()
69 | {
70 | return JsonConvert.SerializeObject(this);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/PrefopResult.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Qiniu.Http;
3 | using System.Text;
4 | namespace Qiniu.Storage
5 | {
6 | ///
7 | /// 查询数据处理状态的返回值
8 | ///
9 | public class PrefopResult:HttpResult
10 | {
11 | ///
12 | /// 持久化任务的状态
13 | ///
14 | public PfopInfo Result
15 | {
16 | get
17 | {
18 | PfopInfo info = null;
19 |
20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
21 | {
22 | info= JsonConvert.DeserializeObject(Text);
23 | }
24 | return info;
25 | }
26 | }
27 |
28 | ///
29 | /// 转换为易读字符串格式
30 | ///
31 | /// 便于打印和阅读的字符串
32 | public override string ToString()
33 | {
34 | StringBuilder sb = new StringBuilder();
35 |
36 | sb.AppendFormat("code: {0}\n", Code);
37 |
38 | if (this.Result!=null)
39 | {
40 | sb.AppendFormat("result: {0}\n", JsonConvert.SerializeObject(this.Result));
41 | }
42 | else
43 | {
44 | if (!string.IsNullOrEmpty(Text))
45 | {
46 | sb.AppendLine("text:");
47 | sb.AppendLine(Text);
48 | }
49 | }
50 | sb.AppendLine();
51 |
52 | sb.AppendFormat("ref-code:{0}\n", RefCode);
53 |
54 | if (!string.IsNullOrEmpty(RefText))
55 | {
56 | sb.AppendLine("ref-text:");
57 | sb.AppendLine(RefText);
58 | }
59 |
60 | if (RefInfo != null)
61 | {
62 | sb.AppendFormat("ref-info:\n");
63 | foreach (var d in RefInfo)
64 | {
65 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value));
66 | }
67 | }
68 |
69 | return sb.ToString();
70 | }
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/FetchResult.cs:
--------------------------------------------------------------------------------
1 | using Qiniu.Http;
2 | using Newtonsoft.Json;
3 | using System.Text;
4 | namespace Qiniu.Storage
5 | {
6 | ///
7 | /// 文件抓取返回的消息
8 | ///
9 | public class FetchResult : HttpResult
10 | {
11 | ///
12 | /// Fetch信息列表
13 | ///
14 | public FetchInfo Result
15 | {
16 | get
17 | {
18 | FetchInfo info = null;
19 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
20 | {
21 | info = JsonConvert.DeserializeObject(Text);
22 | }
23 | return info;
24 | }
25 | }
26 |
27 | ///
28 | /// 转换为易读字符串格式
29 | ///
30 | /// 便于打印和阅读的字符串
31 | public override string ToString()
32 | {
33 | StringBuilder sb = new StringBuilder();
34 |
35 | sb.AppendFormat("code: {0}\n", Code);
36 |
37 | if (Result != null)
38 | {
39 | sb.AppendFormat("Key={0}, Size={1}, Type={2}, Hash={3}\n",
40 | Result.Key, Result.Fsize, Result.MimeType, Result.Hash);
41 | }
42 | else
43 | {
44 | if (!string.IsNullOrEmpty(Text))
45 | {
46 | sb.AppendLine("text:");
47 | sb.AppendLine(Text);
48 | }
49 | }
50 | sb.AppendLine();
51 |
52 | sb.AppendFormat("ref-code: {0}\n", RefCode);
53 |
54 | if (!string.IsNullOrEmpty(RefText))
55 | {
56 | sb.AppendLine("ref-text:");
57 | sb.AppendLine(RefText);
58 | }
59 |
60 | if (RefInfo != null)
61 | {
62 | sb.AppendFormat("ref-info:\n");
63 | foreach (var d in RefInfo)
64 | {
65 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value));
66 | }
67 | }
68 |
69 | return sb.ToString();
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/ListItem.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | namespace Qiniu.Storage
3 | {
4 | ///
5 | /// 文件描述
6 | ///
7 | public class ListItem
8 | {
9 | ///
10 | /// 文件名
11 | ///
12 | [JsonProperty("key")]
13 | public string Key { get; set; }
14 |
15 | ///
16 | /// 文件hash(ETAG)
17 | ///
18 | [JsonProperty("hash")]
19 | public string Hash { get; set; }
20 |
21 | ///
22 | /// 文件大小(字节)
23 | ///
24 | [JsonProperty("fsize")]
25 | public long Fsize { get; set; }
26 |
27 | ///
28 | /// 文件MIME类型
29 | ///
30 | [JsonProperty("mimeType")]
31 | public string MimeType { get; set; }
32 |
33 | ///
34 | /// 上传时间
35 | ///
36 | [JsonProperty("putTime")]
37 | public long PutTime { get; set; }
38 |
39 | ///
40 | /// 文件存储类型
41 | /// 0 标准存储
42 | /// 1 低频存储
43 | /// 2 归档存储
44 | /// 3 深度归档存储
45 | /// 4 归档直读存储
46 | ///
47 | [JsonProperty("type")]
48 | public int FileType { get; set; }
49 |
50 | ///
51 | /// 资源内容的唯一属主标识
52 | /// 详见上传策略:https://developer.qiniu.com/kodo/1206/put-policy
53 | ///
54 | [JsonProperty("endUser", NullValueHandling = NullValueHandling.Ignore)]
55 | public string EndUser { get; set; }
56 |
57 | ///
58 | /// 文件的存储状态
59 | /// 0 启用
60 | /// 1 禁用
61 | ///
62 | [JsonProperty("status")]
63 | public int Status { get; set; }
64 |
65 | ///
66 | /// 文件的 md5 值
67 | /// 服务端不确保一定返回此字段,详见:
68 | /// https://developer.qiniu.com/kodo/1284/list#:~:text=%E3%80%82%0A%0A%E7%B1%BB%E5%9E%8B%EF%BC%9A%E6%95%B0%E5%AD%97-,md5,-%E5%90%A6
69 | ///
70 | [JsonProperty("md5", NullValueHandling = NullValueHandling.Ignore)]
71 | public string Md5 { get; set; }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Qiniu/Util/Base64.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | namespace Qiniu.Util
5 | {
6 | ///
7 | /// Base64 编码/解码
8 | ///
9 | public class Base64
10 | {
11 | ///
12 | /// 获取字符串Url安全Base64编码值
13 | ///
14 | /// 源字符串
15 | /// 已编码字符串
16 | public static string UrlSafeBase64Encode(string text)
17 | {
18 | return UrlSafeBase64Encode(Encoding.UTF8.GetBytes(text));
19 | }
20 |
21 | ///
22 | /// URL安全的base64编码
23 | ///
24 | /// 需要编码的字节数据
25 | ///
26 | public static string UrlSafeBase64Encode(byte[] data)
27 | {
28 | return Convert.ToBase64String(data).Replace('+', '-').Replace('/', '_');
29 | }
30 |
31 | ///
32 | /// bucket:key 编码
33 | ///
34 | /// 空间名称
35 | /// 文件key
36 | /// 编码
37 | public static string UrlSafeBase64Encode(string bucket, string key)
38 | {
39 | return UrlSafeBase64Encode(bucket + ":" + key);
40 | }
41 |
42 | ///
43 | /// Base64解码
44 | ///
45 | /// 待解码的字符串
46 | /// 已解码字符串
47 | public static byte[] UrlsafeBase64Decode(string text)
48 | {
49 | return Convert.FromBase64String(text.Replace('-', '+').Replace('_', '/'));
50 | }
51 |
52 | ///
53 | /// 获取EncodedObjectName,建议仅用于分片上传 V2
54 | ///
55 | /// 待加密的字符串
56 | /// 已加密的字符串
57 | public static string GetEncodedObjectName(string key)
58 | {
59 | string encodedObjectName = "~";
60 | if (key != null)
61 | {
62 | encodedObjectName = UrlSafeBase64Encode(key);
63 | }
64 | return encodedObjectName;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/StatResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Qiniu.Http;
3 | using Newtonsoft.Json;
4 |
5 | namespace Qiniu.Storage
6 | {
7 | ///
8 | /// 获取空间文件信息(stat操作)的返回消息
9 | ///
10 | public class StatResult : HttpResult
11 | {
12 | ///
13 | /// stat信息列表
14 | ///
15 | public FileInfo Result
16 | {
17 | get
18 | {
19 | FileInfo info = null;
20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
21 | {
22 | info = JsonConvert.DeserializeObject(Text);
23 | }
24 | return info;
25 | }
26 | }
27 |
28 | ///
29 | /// 转换为易读字符串格式
30 | ///
31 | /// 便于打印和阅读的字符串
32 | public override string ToString()
33 | {
34 | StringBuilder sb = new StringBuilder();
35 |
36 | sb.AppendFormat("code: {0}\n", Code);
37 |
38 | if (Result != null)
39 | {
40 | sb.AppendFormat("Size={0}, Type={1}, Hash={2}, Time={3}\n",
41 | Result.Fsize, Result.MimeType, Result.Hash, Result.PutTime);
42 |
43 | }
44 | else
45 | {
46 | if (!string.IsNullOrEmpty(Text))
47 | {
48 | sb.AppendLine("text:");
49 | sb.AppendLine(Text);
50 | }
51 | }
52 | sb.AppendLine();
53 |
54 | sb.AppendFormat("ref-code: {0}\n", RefCode);
55 |
56 | if (!string.IsNullOrEmpty(RefText))
57 | {
58 | sb.AppendLine("ref-text:");
59 | sb.AppendLine(RefText);
60 | }
61 |
62 | if (RefInfo != null)
63 | {
64 | sb.AppendFormat("ref-info:\n");
65 | foreach (var d in RefInfo)
66 | {
67 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value));
68 | }
69 | }
70 |
71 | return sb.ToString();
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/BucketsResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json;
4 | using Qiniu.Http;
5 |
6 | namespace Qiniu.Storage
7 | {
8 | ///
9 | /// 获取空间列表-结果
10 | ///
11 | public class BucketsResult:HttpResult
12 | {
13 | ///
14 | /// 空间列表
15 | ///
16 | public List Result
17 | {
18 | get
19 | {
20 | List buckets = null;
21 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
22 | {
23 | buckets = JsonConvert.DeserializeObject>(Text);
24 | }
25 | return buckets;
26 | }
27 | }
28 |
29 | ///
30 | /// 转换为易读字符串格式
31 | ///
32 | /// 便于打印和阅读的字符串
33 | public override string ToString()
34 | {
35 | StringBuilder sb = new StringBuilder();
36 |
37 | sb.AppendFormat("code: {0}\n", Code);
38 |
39 | if (Result != null)
40 | {
41 | sb.AppendLine("bucket(s):");
42 | foreach(var b in Result)
43 | {
44 | sb.AppendLine(b);
45 | }
46 | }
47 | else
48 | {
49 | if (!string.IsNullOrEmpty(Text))
50 | {
51 | sb.AppendLine("text:");
52 | sb.AppendLine(Text);
53 | }
54 | }
55 | sb.AppendLine();
56 |
57 | sb.AppendFormat("ref-code: {0}\n", RefCode);
58 |
59 | if (!string.IsNullOrEmpty(RefText))
60 | {
61 | sb.AppendLine("ref-text:");
62 | sb.AppendLine(RefText);
63 | }
64 |
65 | if (RefInfo != null)
66 | {
67 | sb.AppendFormat("ref-info:\n");
68 | foreach (var d in RefInfo)
69 | {
70 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value));
71 | }
72 | }
73 |
74 | return sb.ToString();
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Qiniu/Http/Middleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Qiniu.Http
5 | {
6 | public delegate HttpResult DNextSend(HttpRequestOptions reqOpts);
7 |
8 | public interface IMiddleware
9 | {
10 | HttpResult Send(HttpRequestOptions req, DNextSend next);
11 | }
12 |
13 | public delegate bool DRetryCondition(HttpResult respResult, HttpRequestOptions reqOpts);
14 |
15 | public class RetryDomainsMiddleware : IMiddleware
16 | {
17 | private List _backupDomains;
18 |
19 | private int _maxRetryTimes;
20 |
21 | private DRetryCondition _retryCondition;
22 |
23 | public RetryDomainsMiddleware(
24 | List backupDomains,
25 | int maxRetryTimes = 2,
26 | DRetryCondition retryCondition = null
27 | )
28 | {
29 | _backupDomains = backupDomains;
30 | _maxRetryTimes = maxRetryTimes;
31 | _retryCondition = retryCondition;
32 | }
33 |
34 | public HttpResult Send(HttpRequestOptions reqOpts, DNextSend next)
35 | {
36 | HttpResult result = null;
37 |
38 | UriBuilder uriBuilder = new UriBuilder(reqOpts.Url);
39 | List domains = new List(_backupDomains);
40 | domains.Insert(0, uriBuilder.Host);
41 |
42 | foreach (string domain in domains)
43 | {
44 | uriBuilder.Host = domain;
45 | reqOpts.Url = uriBuilder.ToString();
46 |
47 | for (int retriedTimes = 0; retriedTimes < _maxRetryTimes; retriedTimes++)
48 | {
49 | result = next(reqOpts);
50 | if (!ShouldRetry(result, reqOpts))
51 | {
52 | return result;
53 | }
54 | }
55 | }
56 |
57 | return result;
58 | }
59 |
60 | private bool ShouldRetry(HttpResult respResult, HttpRequestOptions reqOpts)
61 | {
62 | if (_retryCondition != null)
63 | {
64 | return _retryCondition(respResult, reqOpts);
65 | }
66 |
67 | return respResult != null && respResult.NeedRetry();
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/src/Qiniu/Storage/DomainsResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json;
4 | using Qiniu.Http;
5 |
6 | namespace Qiniu.Storage
7 | {
8 | ///
9 | /// 获取空间域名(domains操作)的返回消息
10 | ///
11 | public class DomainsResult:HttpResult
12 | {
13 | ///
14 | /// 域名(列表)
15 | ///
16 | public List Result
17 | {
18 | get
19 | {
20 | List domains = null;
21 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
22 | {
23 | domains=JsonConvert.DeserializeObject>(Text);
24 | }
25 | return domains;
26 | }
27 | }
28 |
29 | ///
30 | /// 转换为易读字符串格式
31 | ///
32 | /// 便于打印和阅读的字符串
33 | public override string ToString()
34 | {
35 | StringBuilder sb = new StringBuilder();
36 |
37 | sb.AppendFormat("code: {0}\n", Code);
38 |
39 | sb.AppendLine();
40 |
41 | if (Result != null)
42 | {
43 | sb.AppendLine("domain(s):");
44 | foreach (var d in Result)
45 | {
46 | sb.AppendLine(d);
47 | }
48 | }
49 | else
50 | {
51 | if (!string.IsNullOrEmpty(Text))
52 | {
53 | sb.AppendLine("text:");
54 | sb.AppendLine(Text);
55 | }
56 | }
57 | sb.AppendLine();
58 |
59 | sb.AppendFormat("ref-code: {0}\n", RefCode);
60 |
61 | if (!string.IsNullOrEmpty(RefText))
62 | {
63 | sb.AppendLine("ref-text:");
64 | sb.AppendLine(RefText);
65 | }
66 |
67 | if (RefInfo != null)
68 | {
69 | sb.AppendFormat("ref-info:\n");
70 | foreach (var d in RefInfo)
71 | {
72 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value));
73 | }
74 | }
75 |
76 | return sb.ToString();
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/FluxInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System;
3 | namespace Qiniu.CDN
4 | {
5 | ///
6 | /// 流量-消息内容结构
7 | /// 说明:
8 | /// 1.返回的数据包含开始日期和结束日期
9 | /// 2.带宽的单位为 byte
10 | /// 3.数据(data)只包含有流量的域名
11 | ///
12 | /// 以下是一个返回结果示例
13 | ///
14 | /// {
15 | /// "code": 200,
16 | /// "error": "",
17 | /// "time": ["2016-07-01 00:00:00","2016-07-01 00:05:00", ...],
18 | /// "data": {
19 | /// "a.com": {
20 | /// "china": [8888, 9999, 10000, ...],
21 | /// "oversea": [3333, 4444, 5000, ...],
22 | /// },
23 | /// "b.com": {
24 | /// "china": [8888, 9999, 10000, ...],
25 | /// "oversea": [3333, 4444, 5000, ...],
26 | /// }
27 | /// }
28 | /// }
29 | ///
30 | /// 另请参阅 http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html#batch-flux
31 | ///
32 | public class FluxInfo
33 | {
34 | ///
35 | /// 代码 含义 说明
36 | /// 200 success 成功(OK)
37 | /// 400032 invalid host 请求中存在无效的域名,请确保域名格式正确
38 | /// 400080 invalid start time 开始时间格式错误
39 | /// 400081 invalid end time 截止时间格式错误
40 | /// 400082 invalid time range 时间范围错误,请确保开始时间早于结束时间,且时间范围不超过 30 天
41 | /// 500000 internal error 服务端内部错误,请联系技术支持
42 | ///
43 | public int Code { get; set; }
44 |
45 | ///
46 | /// 错误消息(状态码非OK时)
47 | ///
48 | public string Error { get; set; }
49 |
50 | ///
51 | /// 时间点列表
52 | ///
53 | public List Time { get; set; }
54 |
55 | ///
56 | /// 流量数居(与时间点列表对应)
57 | ///
58 | public Dictionary Data { get; set; }
59 |
60 | ///
61 | /// 流量-数据内容
62 | ///
63 | public class FluxData
64 | {
65 | ///
66 | /// 国内流量数据
67 | ///
68 | public List China { get; set; }
69 |
70 | ///
71 | /// 海外流量数据
72 | ///
73 | public List Oversea { get; set; }
74 | }
75 |
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/BandwidthInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System;
3 | namespace Qiniu.CDN
4 | {
5 | ///
6 | /// 带宽-消息内容结构
7 | /// 说明:
8 | /// 1.返回的数据包含开始日期和结束日期
9 | /// 2.带宽的单位为 bps
10 | /// 3.数据(data)只包含有流量的域名
11 | /// 以下是一个返回结果示例
12 | ///
13 | /// 200 OK HTTP/1.1
14 | /// {
15 | /// "code": 200,
16 | /// "error": "",
17 | /// "time": ["2016-07-01 00:00:00","2016-07-01 00:05:00", ...],
18 | /// "data": {
19 | /// "a.com": {
20 | /// "china": [8888, 9999, 10000, ...],
21 | /// "oversea": [3333, 4444, 5000, ...],
22 | /// },
23 | /// "b.com": {
24 | /// "china": [8888, 9999, 10000, ...],
25 | /// "oversea": [3333, 4444, 5000, ...],
26 | /// }
27 | /// }
28 | /// }
29 | ///
30 | /// 另请参阅 http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html#batch-bandwidth
31 | ///
32 | public class BandwidthInfo
33 | {
34 | ///
35 | /// 代码 含义 说明
36 | /// 200 success 成功(OK)
37 | /// 400032 invalid host 请求中存在无效的域名,请确保域名格式正确
38 | /// 400080 invalid start time 开始时间格式错误
39 | /// 400081 invalid end time 截止时间格式错误
40 | /// 400082 invalid time range 时间范围错误,请确保开始时间早于结束时间,且时间范围不超过 30 天
41 | /// 500000 internal error 服务端内部错误,请联系技术支持
42 | ///
43 | public int Code { get; set; }
44 |
45 | ///
46 | /// 错误消息(状态码非OK时)
47 | ///
48 | public string Error { get; set; }
49 |
50 | ///
51 | /// 时间点列表
52 | ///
53 | public List Time { get; set; }
54 |
55 | ///
56 | /// 带宽数居(与时间点列表对应)
57 | /// 数据内容请参见该类型说明
58 | ///
59 | public Dictionary Data { get; set; }
60 | }
61 |
62 | ///
63 | /// 带宽-数据内容
64 | ///
65 | public class BandWidthData
66 | {
67 | ///
68 | /// 国内带宽数据
69 | ///
70 | public List China { get; set; }
71 |
72 | ///
73 | /// 海外带宽数据
74 | ///
75 | public List Oversea { get; set; }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/PfopResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json;
4 | using Qiniu.Http;
5 |
6 | namespace Qiniu.Storage
7 | {
8 | ///
9 | /// 持久化
10 | ///
11 | public class PfopResult : HttpResult
12 | {
13 | ///
14 | /// 此ID可用于查询持久化进度
15 | ///
16 | public string PersistentId
17 | {
18 | get
19 | {
20 | string pid = null;
21 |
22 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
23 | {
24 | Dictionary ret= JsonConvert.DeserializeObject>(Text);
25 | if (ret.ContainsKey("persistentId"))
26 | {
27 | pid = ret["persistentId"];
28 | }
29 | }
30 | return pid;
31 | }
32 | }
33 |
34 | ///
35 | /// 转换为易读字符串格式
36 | ///
37 | /// 便于打印和阅读的字符串
38 | public override string ToString()
39 | {
40 | StringBuilder sb = new StringBuilder();
41 |
42 | sb.AppendFormat("code: {0}\n", Code);
43 |
44 | if (!string.IsNullOrEmpty(PersistentId))
45 | {
46 | sb.AppendFormat("PersistentId: {0}\n", PersistentId);
47 | }
48 | else
49 | {
50 | if (!string.IsNullOrEmpty(Text))
51 | {
52 | sb.AppendLine("text:");
53 | sb.AppendLine(Text);
54 | }
55 | }
56 | sb.AppendLine();
57 |
58 | sb.AppendFormat("ref-code:{0}\n", RefCode);
59 |
60 | if (!string.IsNullOrEmpty(RefText))
61 | {
62 | sb.AppendLine("ref-text:");
63 | sb.AppendLine(RefText);
64 | }
65 |
66 | if (RefInfo != null)
67 | {
68 | sb.AppendFormat("ref-info:\n");
69 | foreach (var d in RefInfo)
70 | {
71 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value));
72 | }
73 | }
74 |
75 | return sb.ToString();
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/RefreshInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Qiniu.CDN
4 | {
5 | ///
6 | /// 缓存刷新-消息内容结构
7 | ///
8 | /// 在请求成功时 code 为 200,requestId、urlQuotaDay、urlSurplusDay、dirQuotaDay、dirSurplusDay才会有有效值,否则为空。
9 | /// 在请求失败时 code 为非 200,error 中包含描述信息。
10 | ///
11 | /// 以下是一个返回结果示例
12 | ///
13 | /// {
14 | /// "code":200,
15 | /// "error":"success",
16 | /// "requestId":"575d1930f9537d3f2600003d",
17 | /// "invalidUrls":null,
18 | /// "invalidDirs":null,
19 | /// "urlQuotaDay":100,
20 | /// "urlSurplusDay":99,
21 | /// "dirQuotaDay":10,
22 | /// "dirSurplusDay":10
23 | /// }
24 | ///
25 | ///
26 | public class RefreshInfo
27 | {
28 | ///
29 | /// 代码 含义 说明
30 | /// 200 success 成功(OK)
31 | /// 400031 invalid url 请求中存在无效的 url,请确保 url 格式正确
32 | /// 400032 invalid host 请求中存在无效的域名,请确保域名格式正确
33 | /// 400034 refresh url limit error 请求次数超出当日刷新限额
34 | /// 400036 invalid request id 无效的请求 id
35 | /// 400037 url has existed url 正在刷新中
36 | /// 500000 internal error 服务端内部错误,请联系技术支持
37 | ///
38 | public int Code { get; set; }
39 |
40 | ///
41 | /// 错误消息(状态码非OK时)
42 | ///
43 | public string Error { get; set; }
44 |
45 | ///
46 | /// 请求ID(可用于反馈排查)
47 | ///
48 | public string RequestId { get; set; }
49 |
50 | ///
51 | /// 非法URL
52 | ///
53 | public List InvalidUrls { get; set; }
54 |
55 | ///
56 | /// 非法URL目录
57 | ///
58 | public List InvalidDirs { get; set; }
59 |
60 | ///
61 | /// 当日URL刷新限额
62 | ///
63 | public int UrlQuotaDay { get; set; }
64 |
65 | ///
66 | /// 当日剩余URL刷新额度
67 | ///
68 | public int UrlSurplusDay { get; set; }
69 |
70 | ///
71 | /// 当日URL目录刷新限额
72 | ///
73 | public int DirQuotaDay { get; set; }
74 |
75 | ///
76 | /// 当日剩余URL目录刷新额度
77 | ///
78 | public int DirSurplusDay { get; set; }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/BucketResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Newtonsoft.Json;
3 | using Qiniu.Http;
4 |
5 | namespace Qiniu.Storage
6 | {
7 | ///
8 | /// 获取bucket信息-结果
9 | ///
10 | public class BucketResult : HttpResult
11 | {
12 | ///
13 | /// bucket信息
14 | ///
15 | public BucketInfo Result
16 | {
17 | get
18 | {
19 | BucketInfo info = null;
20 |
21 | if (Code == (int)HttpCode.OK && !string.IsNullOrEmpty(Text))
22 | {
23 | info= JsonConvert.DeserializeObject(Text);
24 | }
25 |
26 | return info;
27 | }
28 | }
29 |
30 | ///
31 | /// 转换为易读字符串格式
32 | ///
33 | /// 便于打印和阅读的字符串
34 | public override string ToString()
35 | {
36 | StringBuilder sb = new StringBuilder();
37 |
38 | sb.AppendFormat("code: {0}\n", Code);
39 |
40 | if (Result != null)
41 | {
42 | sb.AppendLine("bucket-info:");
43 | sb.AppendFormat("tbl={0}\n", Result.tbl);
44 | sb.AppendFormat("zone={0}\n", Result.zone);
45 | sb.AppendFormat("region={0}\n", Result.region);
46 | sb.AppendFormat("isGlobal={0}\n", Result.global);
47 | sb.AppendFormat("isLine={0}\n", Result.line);
48 | }
49 | else
50 | {
51 | if (!string.IsNullOrEmpty(Text))
52 | {
53 | sb.AppendLine("text:");
54 | sb.AppendLine(Text);
55 | }
56 | }
57 | sb.AppendLine();
58 |
59 | sb.AppendFormat("ref-code: {0}\n", RefCode);
60 |
61 | if (!string.IsNullOrEmpty(RefText))
62 | {
63 | sb.AppendLine("ref-text:");
64 | sb.AppendLine(RefText);
65 | }
66 |
67 | if (RefInfo != null)
68 | {
69 | sb.AppendFormat("ref-info:\n");
70 | foreach (var d in RefInfo)
71 | {
72 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value));
73 | }
74 | }
75 |
76 | return sb.ToString();
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/LogListInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Qiniu.CDN
4 | {
5 | ///
6 | /// 日志-消息内容结构
7 | ///
8 | /// 以下是一个返回结果示例
9 | ///
10 | /// {
11 | /// "data": {
12 | /// "log-test1.SOME_TEST.com": [
13 | /// {
14 | /// "name":"log-test1.SOME_TEST.com_2016-07-01-00_00.gz",
15 | /// "size": 88490306,
16 | /// "mtime": 1466274440,
17 | /// "url": "http://FUSION_LOG_DOWNLOAD_URL1"
18 | /// }
19 | /// ],
20 | /// "log-test2.SOME_TEST.com": [
21 | /// {
22 | /// "name":"log-test2.SOME_TEST.com_2016-07-01-00_00.gz",
23 | /// "size": 73280873,
24 | /// "mtime": 1466273259,
25 | /// "url": "http://FUSION_LOG_DOWNLOAD_URL2"
26 | /// }
27 | /// ]
28 | /// }
29 | /// }
30 | ///
31 | /// ///
32 | public class LogListInfo
33 | {
34 | ///
35 | /// 代码 含义 说明
36 | /// 200 success 成功(OK)
37 | /// 400 invalid params 请求参数格式错误
38 | /// 401 bad token 认证授权失败(包括密钥信息不正确;数字签名错误;授权已超时)
39 | /// 400032 invalid host 请求中存在无效的域名,请确保域名格式正确
40 | /// 500000 internal error 服务器内部错误
41 | ///
42 | public int Code { get; set; }
43 |
44 | ///
45 | /// 错误消息(状态码非OK时)
46 | ///
47 | public string Error { get; set; }
48 |
49 | ///
50 | /// 日志信息(与域名列表对应)
51 | ///
52 | public Dictionary> Data { get; set; }
53 | }
54 |
55 | ///
56 | /// 日志信息内容
57 | ///
58 | public class LogData
59 | {
60 | ///
61 | /// 文件名
62 | ///
63 | public string Name { get; set; }
64 |
65 | ///
66 | /// 文件大小,单位为 Byte
67 | ///
68 | public long Size { get; set; }
69 |
70 | ///
71 | /// 文件修改时间,Unix 时间戳
72 | ///
73 | public long Mtime { get; set; }
74 |
75 | ///
76 | /// 日志下载链接
77 | ///
78 | public string Url { get; set; }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/QiniuTests/Storage/ZoneHelperTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using NUnit.Framework;
3 | using Qiniu.Tests;
4 |
5 | namespace Qiniu.Storage.Tests
6 | {
7 | [TestFixture]
8 | public class ZoneHelperTests : TestEnv
9 | {
10 | [Test]
11 | public void QueryZoneTest()
12 | {
13 | Zone zone = ZoneHelper.QueryZone(AccessKey, Bucket);
14 |
15 | Assert.NotNull(zone);
16 | }
17 |
18 | [Test]
19 | public void QueryZoneWithCustomQueryRegionHost()
20 | {
21 | Config config = new Config();
22 | config.SetQueryRegionHost("uc.qbox.me");
23 | config.UseHttps = true;
24 |
25 | Zone zone = ZoneHelper.QueryZone(
26 | AccessKey,
27 | Bucket,
28 | config.UcHost()
29 | );
30 | Assert.NotNull(zone);
31 | }
32 |
33 | [Test]
34 | public void QueryZoneWithBackupHostsTest()
35 | {
36 | Config config = new Config();
37 | config.SetQueryRegionHost("fake-uc.csharp.qiniu.com");
38 | config.SetBackupQueryRegionHosts(new List
39 | {
40 | "unavailable-uc.csharp.qiniu.com",
41 | "uc.qbox.me"
42 | }
43 | );
44 | config.UseHttps = true;
45 |
46 | Zone zone = ZoneHelper.QueryZone(
47 | AccessKey,
48 | Bucket,
49 | config.UcHost(),
50 | config.BackupQueryRegionHosts()
51 | );
52 | Assert.NotNull(zone);
53 | }
54 |
55 | [Test]
56 | public void QueryZoneWithUcAndBackupHostsTest()
57 | {
58 | Config config = new Config();
59 | config.SetUcHost("fake-uc.csharp.qiniu.com");
60 | config.SetBackupQueryRegionHosts(new List
61 | {
62 | "unavailable-uc.csharp.qiniu.com",
63 | "uc.qbox.me"
64 | }
65 | );
66 | config.UseHttps = true;
67 |
68 | Zone zone = ZoneHelper.QueryZone(
69 | AccessKey,
70 | Bucket,
71 | config.UcHost(),
72 | config.BackupQueryRegionHosts()
73 | );
74 | Assert.NotNull(zone);
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/src/QiniuTests/Http/HttpResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using NUnit.Framework;
5 |
6 | using Qiniu.Http;
7 |
8 | namespace QiniuTests.Http
9 | {
10 | public class NeedRetryDataClass
11 | {
12 | private static readonly IReadOnlyList NotRetryableHttpCodes = new List
13 | {
14 | (int)HttpCode.INVALID_ARGUMENT,
15 | (int)HttpCode.INVALID_FILE,
16 | (int)HttpCode.INVALID_TOKEN,
17 | (int)HttpCode.USER_CANCELED,
18 | (int)HttpCode.USER_PAUSED,
19 | // 服务端
20 | (int)HttpCode.NOT_IMPLEMENTED,
21 | (int)HttpCode.BANDWIDTH_LIMIT_EXCEEDED,
22 | (int)HttpCode.TOO_FREQUENT_ACCESS,
23 | (int)HttpCode.CALLBACK_FAILED,
24 | (int)HttpCode.CONTENT_MODIFIED,
25 | (int)HttpCode.FILE_NOT_EXIST,
26 | (int)HttpCode.FILE_EXISTS,
27 | (int)HttpCode.INVALID_SHARE_BUCKET,
28 | (int)HttpCode.BUCKET_IS_SHARING,
29 | (int)HttpCode.BUCKET_COUNT_LIMIT,
30 | (int)HttpCode.BUCKET_NOT_EXIST,
31 | (int)HttpCode.EXCEED_SHARED_BUCKETS_LIMIT,
32 | (int)HttpCode.INVALID_MARKER,
33 | (int)HttpCode.CONTEXT_EXPIRED
34 | };
35 | public static IEnumerable TestCases
36 | {
37 | get
38 | {
39 | for (int i = -5; i < 800; i++)
40 | {
41 | if (i > 0 && i < 500)
42 | {
43 | yield return new TestCaseData(i).Returns(false);
44 | continue;
45 | }
46 |
47 | if (NotRetryableHttpCodes.Contains(i))
48 | {
49 | yield return new TestCaseData(i).Returns(false);
50 | continue;
51 | }
52 |
53 | yield return new TestCaseData(i).Returns(true);
54 | }
55 | }
56 | }
57 | }
58 |
59 | [TestFixture]
60 | public class HttpResultTests
61 | {
62 | [TestCaseSource(typeof(NeedRetryDataClass), nameof(NeedRetryDataClass.TestCases))]
63 | public bool NeedRetryTest(int code)
64 | {
65 | HttpResult httpResult = new HttpResult
66 | {
67 | Code = code
68 | };
69 | return httpResult.NeedRetry();
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/src/Qiniu/Storage/ResumeHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using Qiniu.Util;
4 | using Newtonsoft.Json;
5 |
6 | namespace Qiniu.Storage
7 | {
8 | ///
9 | /// 断点续上传辅助函数Load/Save
10 | ///
11 | public class ResumeHelper
12 | {
13 |
14 | ///
15 | /// 生成默认的断点记录文件名称
16 | ///
17 | /// 待上传的本地文件
18 | /// 要保存的目标key
19 | /// 用于记录断点信息的文件名
20 | public static string GetDefaultRecordKey(string localFile, string key)
21 | {
22 | string tempDir = System.IO.Path.GetTempPath();
23 | System.IO.FileInfo fileInfo = new System.IO.FileInfo(localFile);
24 | string uniqueKey = string.Format("{0}:{1}:{2}", localFile, key, fileInfo.LastWriteTime.ToFileTime());
25 | return Path.Combine(tempDir, "QiniuResume_" + Hashing.CalcMD5X(uniqueKey));
26 | }
27 |
28 | ///
29 | /// 尝试从从文件载入断点信息
30 | ///
31 | /// 断点记录文件
32 | /// 断点信息
33 | public static ResumeInfo Load(string recordFile)
34 | {
35 | ResumeInfo resumeInfo = null;
36 |
37 | try
38 | {
39 | using (FileStream fs = new FileStream(recordFile, FileMode.Open))
40 | {
41 | using (StreamReader sr = new StreamReader(fs))
42 | {
43 | string jsonStr = sr.ReadToEnd();
44 | resumeInfo=JsonConvert.DeserializeObject(jsonStr);
45 | }
46 | }
47 | }
48 | catch (Exception)
49 | {
50 | resumeInfo = null;
51 | }
52 |
53 | return resumeInfo;
54 | }
55 |
56 | ///
57 | /// 保存断点信息到文件
58 | ///
59 | /// 断点信息
60 | /// 断点记录文件
61 | public static void Save(ResumeInfo resumeInfo, string recordFile)
62 | {
63 | string jsonStr = resumeInfo.ToJsonStr();
64 |
65 | using (FileStream fs = new FileStream(recordFile, FileMode.Create))
66 | {
67 | using (StreamWriter sw = new StreamWriter(fs))
68 | {
69 | sw.Write(jsonStr);
70 | }
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/ChunkUnit.cs:
--------------------------------------------------------------------------------
1 | namespace Qiniu.Storage
2 | {
3 | ///
4 | /// 分片大小
5 | ///
6 | public enum ChunkUnit
7 | {
8 | ///
9 | /// 128KB
10 | ///
11 | U128K = 1,
12 |
13 | ///
14 | /// 256KB
15 | ///
16 | U256K = 2,
17 |
18 | ///
19 | /// 512KB
20 | ///
21 | U512K = 4,
22 |
23 | ///
24 | /// 1MB
25 | ///
26 | U1024K = 8,
27 |
28 | ///
29 | /// 2MB
30 | ///
31 | U2048K = 16,
32 |
33 | ///
34 | /// 4MB
35 | ///
36 | U4096K = 32
37 | };
38 |
39 | ///
40 | /// ChunkSize转换
41 | ///
42 | public class ResumeChunk
43 | {
44 | private static int N = 128 * 1024;
45 |
46 | ///
47 | /// 计算ChunkSize
48 | ///
49 | ///
50 | ///
51 | public static int GetChunkSize(ChunkUnit cu)
52 | {
53 | int c = (int)cu;
54 | return c * N;
55 | }
56 |
57 | ///
58 | /// 计算ChunkUnit
59 | ///
60 | ///
61 | ///
62 | public static ChunkUnit GetChunkUnit(int chunkSize)
63 | {
64 | if (chunkSize < 128 * 1024 || chunkSize > 4 * 1024 * 1024)
65 | {
66 | return ChunkUnit.U2048K;
67 | }
68 | else
69 | {
70 | int u = chunkSize / N;
71 | int cu;
72 |
73 | if (u == 1)
74 | {
75 | cu = 1;
76 | }
77 | else if (u < 4)
78 | {
79 | cu = 2;
80 | }
81 | else if (u < 8)
82 | {
83 | cu = 4;
84 | }
85 | else if (u < 16)
86 | {
87 | cu = 8;
88 | }
89 | else if (u < 32)
90 | {
91 | cu = 16;
92 | }
93 | else
94 | {
95 | cu = 32;
96 | }
97 |
98 | return (ChunkUnit)cu;
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/LogListRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json;
4 | namespace Qiniu.CDN
5 | {
6 | ///
7 | /// 查询日志-请求
8 | ///
9 | public class LogListRequest
10 | {
11 | ///
12 | /// 日期,例如 2016-09-01
13 | ///
14 | [JsonProperty("day")]
15 | public string Day { get; set; }
16 |
17 | ///
18 | /// 域名列表,以西文半角分号分割
19 | ///
20 | [JsonProperty("domains")]
21 | public string Domains { get; set; }
22 |
23 | ///
24 | /// 初始化(所有成员为空,需要后续赋值)
25 | ///
26 | public LogListRequest()
27 | {
28 | Day = "";
29 | Domains = "";
30 | }
31 |
32 | ///
33 | /// 初始化所有成员
34 | ///
35 | /// 日期
36 | /// 域名列表(多个域名以;分隔的字符串)
37 | public LogListRequest(string day, string domains)
38 | {
39 | Day = day;
40 | Domains = domains;
41 | }
42 |
43 | ///
44 | /// 初始化所有成员
45 | ///
46 | /// 日期
47 | /// 域名列表
48 | public LogListRequest(string day, IList domains)
49 | {
50 | if (string.IsNullOrEmpty(day))
51 | {
52 | Day = "";
53 | }
54 | else
55 | {
56 | Day = day;
57 | }
58 |
59 | if (domains == null)
60 | {
61 | Domains = "";
62 | }
63 | else
64 | {
65 | List uniqueDomains = new List();
66 | foreach (string d in domains)
67 | {
68 | if (!uniqueDomains.Contains(d))
69 | {
70 | uniqueDomains.Add(d);
71 | }
72 | }
73 |
74 | if (uniqueDomains.Count > 0)
75 | {
76 | Domains = string.Join(";", uniqueDomains);
77 | }
78 | else
79 | {
80 | Domains = "";
81 | }
82 | }
83 | }
84 |
85 | ///
86 | /// 转换到JSON字符串
87 | ///
88 | /// 请求内容的JSON字符串
89 | public string ToJsonStr()
90 | {
91 | return JsonConvert.SerializeObject(this);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Qiniu/Util/Hashing.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | #if WINDOWS_UWP
3 | using Windows.Security.Cryptography;
4 | using Windows.Security.Cryptography.Core;
5 | #else
6 | using System.Security.Cryptography;
7 | #endif
8 |
9 | namespace Qiniu.Util
10 | {
11 | ///
12 | /// 计算hash值
13 | /// 特别注意,不同平台使用的Cryptography可能略有不同,使用中如有遇到问题,请反馈
14 | /// 提交您的issue到 https://github.com/qiniu/csharp-sdk
15 | ///
16 | public class Hashing
17 | {
18 | ///
19 | /// 计算SHA1
20 | ///
21 | /// 字节数据
22 | /// SHA1
23 | public static byte[] CalcSHA1(byte[] data)
24 | {
25 | #if WINDOWS_UWP
26 | var sha = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha1);
27 | var buf = CryptographicBuffer.CreateFromByteArray(data);
28 | var digest = sha.HashData(buf);
29 | var hashBytes = new byte[digest.Length];
30 | CryptographicBuffer.CopyToByteArray(digest, out hashBytes);
31 | return hashBytes;
32 | #else
33 | SHA1 sha1 = SHA1.Create();
34 | return sha1.ComputeHash(data);
35 | #endif
36 | }
37 |
38 | ///
39 | /// 计算MD5哈希(可能需要关闭FIPS)
40 | ///
41 | /// 待计算的字符串
42 | /// MD5结果
43 | public static string CalcMD5(string str)
44 | {
45 | #if WINDOWS_UWP
46 | var md5 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Md5);
47 | var buf = CryptographicBuffer.ConvertStringToBinary(str, BinaryStringEncoding.Utf8);
48 | var digest = md5.HashData(buf);
49 | return CryptographicBuffer.EncodeToHexString(digest);
50 | #else
51 | MD5 md5 = MD5.Create();
52 | byte[] data = Encoding.UTF8.GetBytes(str);
53 | byte[] hashData = md5.ComputeHash(data);
54 | StringBuilder sb = new StringBuilder(hashData.Length * 2);
55 | foreach (byte b in hashData)
56 | {
57 | sb.AppendFormat("{0:x2}", b);
58 | }
59 | return sb.ToString();
60 | #endif
61 | }
62 |
63 | ///
64 | /// 计算MD5哈希(第三方实现)
65 | ///
66 | /// 待计算的字符串,避免FIPS-Exception
67 | /// MD5结果
68 | public static string CalcMD5X(string str)
69 | {
70 | byte[] data = Encoding.UTF8.GetBytes(str);
71 | LabMD5 md5 = new LabMD5();
72 | return md5.ComputeHash(data);
73 | }
74 |
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/QiniuTests/Http/HttpManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Specialized;
3 | using System.Reflection;
4 | using NUnit.Framework;
5 | using Qiniu.Http;
6 | using Qiniu.Util;
7 |
8 | namespace QiniuTests.Http
9 | {
10 | [TestFixture]
11 | public class HttpManagerTests
12 | {
13 | private static HttpManager httpManager = new HttpManager();
14 | private static MethodInfo dynMethod = httpManager.GetType().GetMethod("addAuthHeaders",
15 | BindingFlags.NonPublic | BindingFlags.Instance);
16 | private static Mac mac = new Mac("ak", "sk");
17 |
18 | [TearDown]
19 | public void EachTeardown()
20 | {
21 | Environment.SetEnvironmentVariable("DISABLE_QINIU_TIMESTAMP_SIGNATURE", null);
22 | }
23 |
24 | [Test]
25 | public void DisableQiniuTimestampSignatureDefaultTest()
26 | {
27 | StringDictionary headers = new StringDictionary();
28 | Auth auth = new Auth(mac);
29 | dynMethod.Invoke(httpManager, new object[] { headers, auth });
30 |
31 | Assert.True(headers.ContainsKey("X-Qiniu-Date"));
32 | }
33 |
34 | [Test]
35 | public void DisableQiniuTimestampSignatureTest()
36 | {
37 | StringDictionary headers = new StringDictionary();
38 | Auth auth = new Auth(mac, new AuthOptions
39 | {
40 | DisableQiniuTimestampSignature = true
41 | });
42 | dynMethod.Invoke(httpManager, new object[] { headers, auth });
43 |
44 | Assert.False(headers.ContainsKey("X-Qiniu-Date"));
45 | }
46 |
47 | [Test]
48 | public void DisableQiniuTimestampSignatureEnvTest()
49 | {
50 | Environment.SetEnvironmentVariable("DISABLE_QINIU_TIMESTAMP_SIGNATURE", "true");
51 |
52 | StringDictionary headers = new StringDictionary();
53 | Auth auth = new Auth(mac);
54 | dynMethod.Invoke(httpManager, new object[] { headers, auth });
55 |
56 | Assert.False(headers.ContainsKey("X-Qiniu-Date"));
57 | }
58 |
59 | [Test]
60 | public void DisableQiniuTimestampSignatureEnvBeIgnoredTest()
61 | {
62 | Environment.SetEnvironmentVariable("DISABLE_QINIU_TIMESTAMP_SIGNATURE", "true");
63 |
64 | StringDictionary headers = new StringDictionary();
65 | Auth auth = new Auth(mac, new AuthOptions
66 | {
67 | DisableQiniuTimestampSignature = false
68 | });
69 | dynMethod.Invoke(httpManager, new object[] { headers, auth });
70 |
71 | Assert.True(headers.ContainsKey("X-Qiniu-Date"));
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/src/Qiniu/Util/UnixTimestamp.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Qiniu.Util
4 | {
5 | ///
6 | /// 时间戳与日期时间转换
7 | ///
8 | public class UnixTimestamp
9 | {
10 | ///
11 | /// 基准时间
12 | ///
13 | private static DateTime dtBase = new DateTime(1970, 1, 1).ToLocalTime();
14 |
15 | ///
16 | /// 时间戳末尾7位(补0或截断)
17 | ///
18 | private const long TICK_BASE = 10000000;
19 |
20 | ///
21 | /// 从现在(调用此函数时刻)起若干秒以后那个时间点的时间戳
22 | ///
23 | /// 从现在起多少秒以后
24 | /// Unix时间戳
25 | public static long GetUnixTimestamp(long secondsAfterNow)
26 | {
27 | DateTime dt = DateTime.Now.AddSeconds(secondsAfterNow).ToLocalTime();
28 | TimeSpan tsx = dt.Subtract(dtBase);
29 | return tsx.Ticks / TICK_BASE;
30 | }
31 |
32 | ///
33 | /// 日期时间转换为时间戳
34 | ///
35 | /// 日期时间
36 | /// 时间戳
37 | public static long ConvertToTimestamp(DateTime dt)
38 | {
39 | TimeSpan tsx = dt.Subtract(dtBase);
40 | return tsx.Ticks / TICK_BASE;
41 | }
42 |
43 | ///
44 | /// 从UNIX时间戳转换为DateTime
45 | ///
46 | /// 时间戳字符串
47 | /// 日期时间
48 | public static DateTime ConvertToDateTime(string timestamp)
49 | {
50 | long ticks = long.Parse(timestamp) * TICK_BASE;
51 | return dtBase.AddTicks(ticks);
52 | }
53 |
54 | ///
55 | /// 从UNIX时间戳转换为DateTime
56 | ///
57 | /// 时间戳
58 | /// 日期时间
59 | public static DateTime ConvertToDateTime(long timestamp)
60 | {
61 | long ticks = timestamp * TICK_BASE;
62 | return dtBase.AddTicks(ticks);
63 | }
64 |
65 | ///
66 | /// 检查Ctx是否过期,我们给当前时间加上一天来看看是否超过了过期时间
67 | /// 而不是直接比较是否超过了过期时间,是给这个文件最大1天的上传持续时间
68 | ///
69 | ///
70 | ///
71 | public static bool IsContextExpired(long expiredAt)
72 | {
73 | if (expiredAt == 0)
74 | {
75 | return false;
76 | }
77 | bool expired = false;
78 | DateTime now = DateTime.Now.AddDays(1);
79 | long nowTs = ConvertToTimestamp(now);
80 | if (nowTs > expiredAt)
81 | {
82 | expired = true;
83 | }
84 | return expired;
85 | }
86 |
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Qiniu/Util/QETag.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Qiniu.Util
5 | {
6 | ///
7 | /// QINIU ETAG(文件hash)
8 | ///
9 | public class QETag
10 | {
11 | // 块大小(固定为4MB)
12 | private const int BLOCK_SIZE = 4 * 1024 * 1024;
13 |
14 | // 计算时以20B为单位
15 | private static int BLOCK_SHA1_SIZE = 20;
16 |
17 | ///
18 | /// 计算文件hash(ETAG)
19 | ///
20 | ///
21 | /// 文件hash
22 | public static string calcHash(string filePath)
23 | {
24 | string qetag = "";
25 |
26 | try
27 | {
28 | using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
29 | {
30 | long fileLength = stream.Length;
31 | byte[] buffer = new byte[BLOCK_SIZE];
32 | byte[] finalBuffer = new byte[BLOCK_SHA1_SIZE + 1];
33 | if (fileLength <= BLOCK_SIZE)
34 | {
35 | int readByteCount = stream.Read(buffer, 0, BLOCK_SIZE);
36 | byte[] readBuffer = new byte[readByteCount];
37 | Array.Copy(buffer, readBuffer, readByteCount);
38 |
39 | byte[] sha1Buffer = Hashing.CalcSHA1(readBuffer);
40 |
41 | finalBuffer[0] = 0x16;
42 | Array.Copy(sha1Buffer, 0, finalBuffer, 1, sha1Buffer.Length);
43 | }
44 | else
45 | {
46 | long blockCount = (fileLength % BLOCK_SIZE == 0) ? (fileLength / BLOCK_SIZE) : (fileLength / BLOCK_SIZE + 1);
47 | byte[] sha1AllBuffer = new byte[BLOCK_SHA1_SIZE * blockCount];
48 |
49 | for (int i = 0; i < blockCount; i++)
50 | {
51 | int readByteCount = stream.Read(buffer, 0, BLOCK_SIZE);
52 | byte[] readBuffer = new byte[readByteCount];
53 | Array.Copy(buffer, readBuffer, readByteCount);
54 |
55 | byte[] sha1Buffer = Hashing.CalcSHA1(readBuffer);
56 | Array.Copy(sha1Buffer, 0, sha1AllBuffer, i * BLOCK_SHA1_SIZE, sha1Buffer.Length);
57 | }
58 |
59 | byte[] sha1AllBufferSha1 = Hashing.CalcSHA1(sha1AllBuffer);
60 |
61 | finalBuffer[0] = 0x96;
62 | Array.Copy(sha1AllBufferSha1, 0, finalBuffer, 1, sha1AllBufferSha1.Length);
63 |
64 | }
65 | qetag = Base64.UrlSafeBase64Encode(finalBuffer);
66 | }
67 | }
68 | catch (Exception) { }
69 |
70 | return qetag;
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/Qiniu/Util/ETag.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Qiniu.Util
5 | {
6 | ///
7 | /// QINIU ETAG(文件hash)
8 | ///
9 | public class ETag
10 | {
11 | // 块大小(固定为4MB)
12 | private const int BLOCK_SIZE = 4 * 1024 * 1024;
13 |
14 | // 计算时以20B为单位
15 | private static int BLOCK_SHA1_SIZE = 20;
16 |
17 | ///
18 | /// 计算文件hash(ETAG)
19 | ///
20 | ///
21 | /// 文件hash
22 | public static string CalcHash(string filePath)
23 | {
24 | string qetag = "";
25 |
26 | try
27 | {
28 | using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
29 | {
30 | long fileLength = stream.Length;
31 | byte[] buffer = new byte[BLOCK_SIZE];
32 | byte[] finalBuffer = new byte[BLOCK_SHA1_SIZE + 1];
33 | if (fileLength <= BLOCK_SIZE)
34 | {
35 | int readByteCount = stream.Read(buffer, 0, BLOCK_SIZE);
36 | byte[] readBuffer = new byte[readByteCount];
37 | Array.Copy(buffer, readBuffer, readByteCount);
38 |
39 | byte[] sha1Buffer = Hashing.CalcSHA1(readBuffer);
40 |
41 | finalBuffer[0] = 0x16;
42 | Array.Copy(sha1Buffer, 0, finalBuffer, 1, sha1Buffer.Length);
43 | }
44 | else
45 | {
46 | long blockCount = (fileLength % BLOCK_SIZE == 0) ? (fileLength / BLOCK_SIZE) : (fileLength / BLOCK_SIZE + 1);
47 | byte[] sha1AllBuffer = new byte[BLOCK_SHA1_SIZE * blockCount];
48 |
49 | for (int i = 0; i < blockCount; i++)
50 | {
51 | int readByteCount = stream.Read(buffer, 0, BLOCK_SIZE);
52 | byte[] readBuffer = new byte[readByteCount];
53 | Array.Copy(buffer, readBuffer, readByteCount);
54 |
55 | byte[] sha1Buffer = Hashing.CalcSHA1(readBuffer);
56 | Array.Copy(sha1Buffer, 0, sha1AllBuffer, i * BLOCK_SHA1_SIZE, sha1Buffer.Length);
57 | }
58 |
59 | byte[] sha1AllBufferSha1 = Hashing.CalcSHA1(sha1AllBuffer);
60 |
61 | finalBuffer[0] = 0x96;
62 | Array.Copy(sha1AllBufferSha1, 0, finalBuffer, 1, sha1AllBufferSha1.Length);
63 |
64 | }
65 | qetag = Base64.UrlSafeBase64Encode(finalBuffer);
66 | }
67 | }
68 | catch (Exception) { }
69 |
70 | return qetag;
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/Qiniu/Http/UrlHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace Qiniu.Http
4 | {
5 | ///
6 | /// URL辅助工具(RegExp)
7 | ///
8 | public class UrlHelper
9 | {
10 | private static Regex regx = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?");
11 |
12 | private static Regex regu = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,/~\+#]*)?");
13 |
14 | private static Regex regd = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,/~\+#]*)?/");
15 |
16 | ///
17 | /// 是否合法URL
18 | ///
19 | /// 待判断的url
20 | ///
21 | public static bool isValidUrl(string _url)
22 | {
23 | return regx.IsMatch(_url);
24 | }
25 |
26 | ///
27 | /// 是否一般URL(不包含?等后缀参数)
28 | ///
29 | /// 待判断的url
30 | ///
31 | public static bool isNormalUrl(string _url)
32 | {
33 | return regu.IsMatch(_url);
34 | }
35 |
36 | ///
37 | /// 是否合法URL目录
38 | ///
39 | /// 待判断的url目录
40 | ///
41 | public static bool isValidDir(string _dir)
42 | {
43 | return regd.IsMatch(_dir);
44 | }
45 |
46 | ///
47 | /// 从原始URL转换为一般URL(根据需要截断)
48 | ///
49 | /// 待转换的url
50 | ///
51 | public static string getNormalUrl(string _url)
52 | {
53 | var m = regu.Match(_url);
54 | return m.Value;
55 | }
56 |
57 | ///
58 | /// URL分析,拆分出Host,Path,File,Query各个部分
59 | ///
60 | /// 原始URL
61 | /// host部分
62 | /// path部分
63 | /// 文件名
64 | /// 参数
65 | public static void urlSplit(string url, out string host, out string path, out string file, out string query)
66 | {
67 | int start = 0;
68 |
69 | Regex regHost = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+");
70 | host = regHost.Match(url, start).Value;
71 | start += host.Length;
72 |
73 | Regex regPath = new Regex(@"(/(\w|\-)*)+/");
74 | path = regPath.Match(url, start).Value;
75 | if (string.IsNullOrEmpty(path))
76 | {
77 | path = "/";
78 | }
79 | start += path.Length;
80 |
81 | int index = url.IndexOf('?', start);
82 | file = url.Substring(start, index - start);
83 |
84 | query = url.Substring(index);
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/PrefetchResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Newtonsoft.Json;
3 | using Qiniu.Http;
4 |
5 | namespace Qiniu.CDN
6 | {
7 | ///
8 | /// 文件预取-结果
9 | ///
10 | public class PrefetchResult : HttpResult
11 | {
12 | ///
13 | /// 获取文件预取信息
14 | ///
15 | public PrefetchInfo Result
16 | {
17 | get
18 | {
19 | PrefetchInfo info = null;
20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
21 | {
22 | info=JsonConvert.DeserializeObject(Text);
23 | }
24 | return info;
25 | }
26 | }
27 |
28 | ///
29 | /// 转换为易读字符串格式
30 | ///
31 | /// 便于打印和阅读的字符串
32 | public override string ToString()
33 | {
34 | StringBuilder sb = new StringBuilder();
35 |
36 | sb.AppendFormat("code:{0}\n", Code);
37 | sb.AppendLine();
38 |
39 | if (Result != null)
40 | {
41 | sb.AppendLine("result:");
42 | sb.AppendFormat("code:{0}\n", Result.Code);
43 | if (!string.IsNullOrEmpty(Result.Error))
44 | {
45 | sb.AppendFormat("error:{0}\n", Result.Error);
46 | }
47 | if (!string.IsNullOrEmpty(Result.RequestId))
48 | {
49 | sb.AppendFormat("requestId:{0}\n", Result.RequestId);
50 | }
51 | if (Result.InvalidUrls != null && Result.InvalidUrls.Count > 0)
52 | {
53 | sb.Append("invalidUrls:");
54 | foreach (var s in Result.InvalidUrls)
55 | {
56 | sb.Append(s + " ");
57 | }
58 | }
59 | sb.AppendLine();
60 | sb.AppendFormat("quotaDay:{0}\n", Result.QuotaDay);
61 | sb.AppendFormat("surplusaDay:{0}\n", Result.SurplusDay);
62 | }
63 | else
64 | {
65 | if (!string.IsNullOrEmpty(Text))
66 | {
67 | sb.AppendLine("text:");
68 | sb.AppendLine(Text);
69 | }
70 | }
71 | sb.AppendLine();
72 |
73 | sb.AppendFormat("ref-code:{0}\n", RefCode);
74 |
75 | if (!string.IsNullOrEmpty(RefText))
76 | {
77 | sb.AppendLine("ref-text:");
78 | sb.AppendLine(RefText);
79 | }
80 |
81 | if (RefInfo != null)
82 | {
83 | sb.AppendFormat("ref-info:\n");
84 | foreach (var d in RefInfo)
85 | {
86 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value));
87 | }
88 | }
89 |
90 | return sb.ToString();
91 | }
92 | }
93 | }
94 |
95 |
--------------------------------------------------------------------------------
/src/Qiniu.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29102.190
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Qiniu", "Qiniu\Qiniu.csproj", "{2F5B0328-DE8B-4B53-A500-3077E340A51B}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QiniuTests", "QiniuTests\QiniuTests.csproj", "{E8CB1665-53F7-46A5-9AFD-B85AD08262D0}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|x64 = Debug|x64
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|x64 = Release|x64
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x64.ActiveCfg = Debug|Any CPU
23 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x64.Build.0 = Debug|Any CPU
24 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x86.ActiveCfg = Debug|Any CPU
25 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x86.Build.0 = Debug|Any CPU
26 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x64.ActiveCfg = Release|Any CPU
29 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x64.Build.0 = Release|Any CPU
30 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x86.ActiveCfg = Release|Any CPU
31 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x86.Build.0 = Release|Any CPU
32 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x64.ActiveCfg = Debug|Any CPU
35 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x64.Build.0 = Debug|Any CPU
36 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x86.ActiveCfg = Debug|Any CPU
37 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x86.Build.0 = Debug|Any CPU
38 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x64.ActiveCfg = Release|Any CPU
41 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x64.Build.0 = Release|Any CPU
42 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x86.ActiveCfg = Release|Any CPU
43 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x86.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | GlobalSection(ExtensibilityGlobals) = postSolution
49 | SolutionGuid = {0358A72B-AB04-47A5-8AC2-BCDE2447D2A4}
50 | EndGlobalSection
51 | EndGlobal
52 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/FluxResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Newtonsoft.Json;
3 | using Qiniu.Http;
4 |
5 | namespace Qiniu.CDN
6 | {
7 | ///
8 | /// 查询流量-结果
9 | ///
10 | public class FluxResult : HttpResult
11 | {
12 | ///
13 | /// 获取流量信息
14 | ///
15 | public FluxInfo Result
16 | {
17 | get
18 | {
19 | FluxInfo info = null;
20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
21 | {
22 | info=JsonConvert.DeserializeObject(Text);
23 | }
24 | return info;
25 | }
26 | }
27 |
28 | ///
29 | /// 转换为易读字符串格式
30 | ///
31 | /// 便于打印和阅读的字符串
32 | public override string ToString()
33 | {
34 | StringBuilder sb = new StringBuilder();
35 |
36 | sb.AppendFormat("code:{0}\n", Code);
37 | sb.AppendLine();
38 |
39 | if (Result != null)
40 | {
41 | sb.AppendLine("result:");
42 | sb.AppendFormat("code:{0}\n", Result.Code);
43 | if (!string.IsNullOrEmpty(Result.Error))
44 | {
45 | sb.AppendFormat("error:{0}\n", Result.Error);
46 | }
47 | if (Result.Time != null)
48 | {
49 | sb.Append("time:");
50 | foreach (var t in Result.Time)
51 | {
52 | sb.Append(t + " ");
53 | }
54 | sb.AppendLine();
55 | }
56 |
57 | if (Result.Data != null && Result.Data.Count > 0)
58 | {
59 | sb.Append("flux:");
60 | foreach (var kvp in Result.Data)
61 | {
62 | sb.AppendFormat("{0}:\nChina: {1}, Oversea={2}\n", kvp.Key, kvp.Value.China, kvp.Value.Oversea);
63 | }
64 | sb.AppendLine();
65 | }
66 | }
67 | else
68 | {
69 | if (!string.IsNullOrEmpty(Text))
70 | {
71 | sb.AppendLine("text:");
72 | sb.AppendLine(Text);
73 | }
74 | }
75 | sb.AppendLine();
76 |
77 | sb.AppendFormat("ref-code:{0}\n", RefCode);
78 |
79 | if (!string.IsNullOrEmpty(RefText))
80 | {
81 | sb.AppendLine("ref-text:");
82 | sb.AppendLine(RefText);
83 | }
84 |
85 | if (RefInfo != null)
86 | {
87 | sb.AppendFormat("ref-info:\n");
88 | foreach (var d in RefInfo)
89 | {
90 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value));
91 | }
92 | }
93 |
94 | return sb.ToString();
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/BandwidthResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Newtonsoft.Json;
3 | using Qiniu.Http;
4 |
5 | namespace Qiniu.CDN
6 | {
7 | ///
8 | /// 查询带宽-结果
9 | ///
10 | public class BandwidthResult : HttpResult
11 | {
12 | ///
13 | /// 获取带宽信息
14 | ///
15 | public BandwidthInfo Result
16 | {
17 | get
18 | {
19 | BandwidthInfo info = null;
20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
21 | {
22 | info = JsonConvert.DeserializeObject(Text);
23 | }
24 | return info;
25 | }
26 | }
27 |
28 | ///
29 | /// 转换为易读字符串格式
30 | ///
31 | /// 便于打印和阅读的字符串
32 | public override string ToString()
33 | {
34 | StringBuilder sb = new StringBuilder();
35 |
36 | sb.AppendFormat("code:{0}\n", Code);
37 |
38 | sb.AppendLine();
39 |
40 | if (Result != null)
41 | {
42 | sb.AppendLine("result:");
43 | sb.AppendFormat("code:{0}\n", Result.Code);
44 | if (!string.IsNullOrEmpty(Result.Error))
45 | {
46 | sb.AppendFormat("error:{0}\n", Result.Error);
47 | }
48 | if (Result.Time != null)
49 | {
50 | sb.Append("time:");
51 | foreach (var t in Result.Time)
52 | {
53 | sb.Append(t + " ");
54 | }
55 | sb.AppendLine();
56 | }
57 |
58 | if (Result.Data != null && Result.Data.Count > 0)
59 | {
60 | sb.Append("bandwidth:");
61 | foreach (var kvp in Result.Data)
62 | {
63 | sb.AppendFormat("{0}:\nChina: {1}, Oversea={2}\n", kvp.Key, kvp.Value.China, kvp.Value.Oversea);
64 | }
65 | sb.AppendLine();
66 | }
67 | }
68 | else
69 | {
70 | if (!string.IsNullOrEmpty(Text))
71 | {
72 | sb.AppendLine("text:");
73 | sb.AppendLine(Text);
74 | }
75 | }
76 | sb.AppendLine();
77 |
78 | sb.AppendFormat("ref-code:{0}\n", RefCode);
79 |
80 | if (!string.IsNullOrEmpty(RefText))
81 | {
82 | sb.AppendLine("ref-text:");
83 | sb.AppendLine(RefText);
84 | }
85 |
86 | if (RefInfo != null)
87 | {
88 | sb.AppendFormat("ref-info:\n");
89 | foreach (var d in RefInfo)
90 | {
91 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value));
92 | }
93 | }
94 |
95 | return sb.ToString();
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/ListResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Newtonsoft.Json;
3 | using Qiniu.Http;
4 |
5 | namespace Qiniu.Storage
6 | {
7 | ///
8 | /// 获取空间文件列表(list操作)的返回消息
9 | ///
10 | public class ListResult:HttpResult
11 | {
12 | ///
13 | /// 文件列表信息
14 | ///
15 | public ListInfo Result
16 | {
17 | get
18 | {
19 | ListInfo info = null;
20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
21 | {
22 | info= JsonConvert.DeserializeObject(Text);
23 | }
24 | return info;
25 | }
26 | }
27 |
28 | ///
29 | /// 转换为易读字符串格式
30 | ///
31 | /// 便于打印和阅读的字符串>
32 | public override string ToString()
33 | {
34 | StringBuilder sb = new StringBuilder();
35 |
36 | sb.AppendFormat("code: {0}\n", Code);
37 |
38 | if (Result != null)
39 | {
40 | if (Result.CommonPrefixes != null)
41 | {
42 | sb.Append("commonPrefixes:");
43 | foreach(var p in Result.CommonPrefixes)
44 | {
45 | sb.AppendFormat("{0} ", p);
46 | }
47 | sb.AppendLine();
48 | }
49 |
50 | if (!string.IsNullOrEmpty(Result.Marker))
51 | {
52 | sb.AppendFormat("marker: {0}\n", Result.Marker);
53 | }
54 |
55 | if (Result.Items != null)
56 | {
57 | sb.AppendLine("items:");
58 | int i = 0, n = Result.Items.Count;
59 | foreach (var item in Result.Items)
60 | {
61 | sb.AppendFormat("#{0}/{1}:Key={2}, Size={3}, Mime={4}, Hash={5}, Time={6}, Type={7}\n",
62 | ++i, n, item.Key, item.Fsize, item.MimeType, item.Hash, item.PutTime, item.FileType);
63 | }
64 | }
65 | }
66 | else
67 | {
68 | if (!string.IsNullOrEmpty(Text))
69 | {
70 | sb.AppendLine("text:");
71 | sb.AppendLine(Text);
72 | }
73 | }
74 | sb.AppendLine();
75 |
76 | sb.AppendFormat("ref-code: {0}\n", RefCode);
77 |
78 | if (!string.IsNullOrEmpty(RefText))
79 | {
80 | sb.AppendLine("ref-text:");
81 | sb.AppendLine(RefText);
82 | }
83 |
84 | if (RefInfo != null)
85 | {
86 | sb.AppendFormat("ref-info:\n");
87 | foreach (var d in RefInfo)
88 | {
89 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value));
90 | }
91 | }
92 |
93 | return sb.ToString();
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Qiniu/Http/HttpHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Qiniu.Util;
3 |
4 | namespace Qiniu.Http
5 | {
6 | ///
7 | /// HTTP辅助工具:帮助生成UA,boundary等
8 | ///
9 | public class HttpHelper
10 | {
11 | ///
12 | /// 资源类型:普通文本
13 | ///
14 | public static string CONTENT_TYPE_TEXT_PLAIN = "text/plain";
15 |
16 | ///
17 | /// 资源类型:JSON字符串
18 | ///
19 | public static string CONTENT_TYPE_APP_JSON = "application/json";
20 |
21 | ///
22 | /// 资源类型:未知类型(数据流)
23 | ///
24 | public static string CONTENT_TYPE_APP_OCTET = "application/octet-stream";
25 |
26 | ///
27 | /// 资源类型:表单数据(键值对)
28 | ///
29 | public static string CONTENT_TYPE_WWW_FORM = "application/x-www-form-urlencoded";
30 |
31 | ///
32 | /// 资源类型:多分部数据
33 | ///
34 | public static string CONTENT_TYPE_MULTIPART = "multipart/form-data";
35 |
36 | ///
37 | /// HTTP状态码200 (OK)
38 | ///
39 | public static int STATUS_CODE_OK = 200;
40 |
41 | ///
42 | /// HTTP状态码298 (部分OK)
43 | ///
44 | public static int STATUS_CODE_PARTLY_OK = 298;
45 |
46 | ///
47 | /// 自定义HTTP状态码 (默认值)
48 | ///
49 | public static int STATUS_CODE_UNDEF = -256;
50 |
51 | ///
52 | /// 自定义HTTP状态码 (用户取消)
53 | ///
54 | public static int STATUS_CODE_USER_CANCELED = -255;
55 |
56 | ///
57 | /// 自定义HTTP状态码 (用户暂停)
58 | ///
59 | public static int STATUS_CODE_USER_PAUSED = -254;
60 |
61 | ///
62 | /// 自定义HTTP状态码 (用户继续)
63 | ///
64 | public static int STATUS_CODE_USER_RESUMED = -253;
65 |
66 | ///
67 | /// 自定义HTTP状态码 (需要重试)
68 | ///
69 | public static int STATUS_CODE_NEED_RETRY= -252;
70 |
71 | ///
72 | /// 自定义HTTP状态码 (异常或错误)
73 | ///
74 | public static int STATUS_CODE_EXCEPTION = -252;
75 |
76 | ///
77 | /// 客户端标识
78 | ///
79 | /// 客户端标识UA
80 | public static string getUserAgent()
81 | {
82 | #if NetStandard
83 | string sfx = Environment.MachineName;
84 | #else
85 | var osInfo = Environment.OSVersion;
86 | string sfx = Environment.MachineName + "; " + osInfo.Platform + "; " + osInfo.Version;
87 | #endif
88 | return string.Format("{0}/{1} ({2})", QiniuCSharpSDK.ALIAS, QiniuCSharpSDK.VERSION, sfx);
89 | }
90 |
91 | ///
92 | /// 多部分表单数据(multi-part form-data)的分界(boundary)标识
93 | ///
94 | /// 多部分表单数据的boundary
95 | public static string createFormDataBoundary()
96 | {
97 | string now = DateTime.UtcNow.Ticks.ToString();
98 | return string.Format("-------{0}Boundary{1}", QiniuCSharpSDK.ALIAS, Hashing.CalcMD5(now));
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/RefreshRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 | using Newtonsoft.Json;
4 | namespace Qiniu.CDN
5 | {
6 | ///
7 | /// 缓存刷新-请求
8 | ///
9 | public class RefreshRequest
10 | {
11 | ///
12 | /// 要预取的单个url列表,总数不超过100条
13 | /// 单个url,即一个具体的url,例如:http://bar.foo.com/test.zip
14 | /// 注意:
15 | /// 请输入资源 url 完整的绝对路径,由 http:// 或 https:// 开始
16 | /// 资源 url 不支持通配符,例如:不支持 http://www.test.com/abc/*.*
17 | /// 带参数的 url 刷新,根据其域名缓存配置是否忽略参数缓存决定刷新结果。
18 | /// 如果配置了时间戳防盗链的资源 url 提交时刷新需要去掉 e 和 token 参数
19 | ///
20 | [JsonProperty("urls", NullValueHandling = NullValueHandling.Ignore)]
21 | public List Urls { get; set; }
22 |
23 | ///
24 | /// 要刷新的目录url列表,总数不超过10条;目录dir,即表示一个目录级的url,需要以 / 结尾
25 | /// 例如:http://bar.foo.com/dir/,
26 | /// 也支持在尾部使用通配符,例如:http://bar.foo.com/dir/*
27 | ///
28 | [JsonProperty("dirs", NullValueHandling = NullValueHandling.Ignore)]
29 | public List Dirs { get; set; }
30 |
31 | ///
32 | /// 初始化(所有成员为空,需要后续赋值)
33 | ///
34 | public RefreshRequest()
35 | {
36 | this.Urls = new List();
37 | this.Dirs = new List();
38 | }
39 |
40 | ///
41 | /// 初始化URL列表
42 | ///
43 | /// URL列表
44 | /// URL目录列表
45 | public RefreshRequest(IList urls, IList dirs)
46 | {
47 | this.Urls = new List();
48 | this.Dirs = new List();
49 |
50 | if (urls != null)
51 | {
52 | AddUrls(urls);
53 | }
54 |
55 | if (dirs != null)
56 | {
57 | AddDirs(dirs);
58 | }
59 | }
60 |
61 | ///
62 | /// 添加URL列表
63 | ///
64 | /// URL列表
65 | public void AddUrls(IList urls)
66 | {
67 | if (urls != null)
68 | {
69 | foreach (var u in urls)
70 | {
71 | if (!this.Urls.Contains(u))
72 | {
73 | this.Urls.Add(u);
74 | }
75 | }
76 | }
77 | }
78 |
79 | ///
80 | /// 添加URL目录列表
81 | ///
82 | /// URL目录列表
83 | public void AddDirs(IList dirs)
84 | {
85 | if (dirs != null)
86 | {
87 | foreach (var d in dirs)
88 | {
89 | if (!this.Dirs.Contains(d))
90 | {
91 | this.Dirs.Add(d);
92 | }
93 | }
94 | }
95 | }
96 |
97 | ///
98 | /// 转换到JSON字符串
99 | ///
100 | /// 请求内容的JSON字符串
101 | public string ToJsonStr()
102 | {
103 | return JsonConvert.SerializeObject(this);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/BatchResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json;
4 | using Qiniu.Http;
5 |
6 | namespace Qiniu.Storage
7 | {
8 | ///
9 | /// 批量处理结果
10 | ///
11 | public class BatchResult : HttpResult
12 | {
13 | ///
14 | /// 错误消息
15 | ///
16 | public string Error
17 | {
18 | get
19 | {
20 | string ex = null;
21 | if (Code != (int)HttpCode.OK && Code != (int)HttpCode.PARTLY_OK)
22 | {
23 |
24 | Dictionary ret = JsonConvert.DeserializeObject>(Text);
25 | if (ret.ContainsKey("error"))
26 | {
27 | ex = ret["error"];
28 | }
29 | }
30 | return ex;
31 | }
32 | }
33 |
34 | ///
35 | /// 获取批量处理结果
36 | ///
37 | public List Result
38 | {
39 | get
40 | {
41 | List info = null;
42 | if ((Code == (int)HttpCode.OK || Code == (int)HttpCode.PARTLY_OK) &&
43 | (!string.IsNullOrEmpty(Text)))
44 | {
45 | info = JsonConvert.DeserializeObject>(Text);
46 | }
47 | return info;
48 | }
49 | }
50 |
51 | ///
52 | /// 转换为易读字符串格式
53 | ///
54 | /// 便于打印和阅读的字符串>
55 | public override string ToString()
56 | {
57 | StringBuilder sb = new StringBuilder();
58 |
59 | sb.AppendFormat("code: {0}\n", Code);
60 |
61 | if (Result != null)
62 | {
63 | sb.AppendLine("result:");
64 | int i = 0, n = Result.Count;
65 | foreach (var v in Result)
66 | {
67 | sb.AppendFormat("#{0}/{1}\n", ++i, n);
68 | sb.AppendFormat("code: {0}\n", v.Code);
69 | sb.AppendFormat("data:\n{0}\n\n", v.Data);
70 | }
71 | }
72 | else
73 | {
74 | if (!string.IsNullOrEmpty(Error))
75 | {
76 | sb.AppendFormat("Error: {0}\n", Error);
77 | }
78 | else if (!string.IsNullOrEmpty(Text))
79 | {
80 | sb.AppendLine("text:");
81 | sb.AppendLine(Text);
82 | }
83 | }
84 | sb.AppendLine();
85 |
86 | sb.AppendFormat("ref-code: {0}\n", RefCode);
87 |
88 | if (!string.IsNullOrEmpty(RefText))
89 | {
90 | sb.AppendLine("ref-text:");
91 | sb.AppendLine(RefText);
92 | }
93 |
94 | if (RefInfo != null)
95 | {
96 | sb.AppendFormat("ref-info:\n");
97 | foreach (var d in RefInfo)
98 | {
99 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value));
100 | }
101 | }
102 |
103 | return sb.ToString();
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Qiniu/Util/UserEnv.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | #if WINDOWS_UWP
4 | using System.Threading.Tasks;
5 | using Windows.Storage;
6 | #else
7 | using System.IO;
8 | #endif
9 |
10 | namespace Qiniu.Util
11 | {
12 | ///
13 | /// 环境变量-用户路径
14 | ///
15 | public class UserEnv
16 | {
17 |
18 | #if Net20 || Net35 || Net40 || Net45 || Net46
19 |
20 | ///
21 | /// 找到QHome目录(在用户目录下建立的"QHome"文件夹)
22 | ///
23 | /// QHOME路径
24 | public static string GetHomeFolder()
25 | {
26 | // Windows下Home目录 = %HOMEDRIVE% + %HOMEPATH%
27 | string homeDir = Environment.GetEnvironmentVariable("HOMEDRIVE") + Environment.GetEnvironmentVariable("HOMEPATH");
28 |
29 | if (string.IsNullOrEmpty(homeDir))
30 | {
31 | // 如果获取失败,就设置为当前路径
32 | homeDir = Path.GetFullPath(".");
33 | }
34 |
35 | string homeFolder = Path.Combine(homeDir, "QHome");
36 |
37 | if(!Directory.Exists(homeFolder))
38 | {
39 | Directory.CreateDirectory(homeFolder);
40 | }
41 |
42 | return homeFolder;
43 | }
44 |
45 | #elif NetCore
46 |
47 | ///
48 | /// 找到QHome目录(在用户目录下建立的"QHome"文件夹)
49 | ///
50 | /// QHOME路径
51 | public static string GetHomeFolder()
52 | {
53 | // Windows下Home目录 = %HOMEDRIVE% + %HOMEPATH%
54 | string homeDir = Environment.GetEnvironmentVariable("HOMEDRIVE") + Environment.GetEnvironmentVariable("HOMEPATH");
55 |
56 | // OSX/Ubuntu下Home目录 = $HOME
57 | if (string.IsNullOrEmpty(homeDir))
58 | {
59 | homeDir = Environment.GetEnvironmentVariable("HOME");
60 | }
61 |
62 | if (string.IsNullOrEmpty(homeDir))
63 | {
64 | // 如果获取失败,就设置为当前路径
65 | homeDir = Path.GetFullPath(".");
66 | }
67 |
68 | string homeFolder = Path.Combine(homeDir, "QHome");
69 |
70 | if(!Directory.Exists(homeFolder))
71 | {
72 | Directory.CreateDirectory(homeFolder);
73 | }
74 |
75 | return homeFolder;
76 | }
77 |
78 | #elif WINDOWS_UWP
79 |
80 | ///
81 | /// 找到QHome目录(在"应用缓存"目录下建立的"QHome"文件夹)
82 | ///
83 | /// QHOME路径
84 | public static async Task GetHomeFolderAsync()
85 | {
86 | return await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("QHome", CreationCollisionOption.OpenIfExists);
87 | //return await KnownFolders.DocumentsLibrary.CreateFolderAsync("QHome", CreationCollisionOption.OpenIfExists);
88 | }
89 |
90 | #else
91 |
92 | ///
93 | /// 找到QHome目录(在当前目录下建立的"QHome"文件夹)
94 | ///
95 | /// QHOME路径
96 | public static string GetHomeFolder()
97 | {
98 | //当前路径
99 | string homeFolder = Path.GetFullPath("./QHome");
100 |
101 | if(!Directory.Exists(homeFolder))
102 | {
103 | Directory.CreateDirectory(homeFolder);
104 | }
105 | return homeFolder;
106 | }
107 |
108 | #endif
109 |
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/LogListResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Newtonsoft.Json;
3 | using Qiniu.Http;
4 |
5 | namespace Qiniu.CDN
6 | {
7 | ///
8 | /// 查询日志-结果
9 | ///
10 | public class LogListResult : HttpResult
11 | {
12 | ///
13 | /// 获取日志列表信息
14 | ///
15 | public LogListInfo Result
16 | {
17 | get
18 | {
19 | LogListInfo info = null;
20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
21 | {
22 | info=JsonConvert.DeserializeObject(Text);
23 | }
24 | return info;
25 | }
26 | }
27 |
28 | ///
29 | /// 转换为易读字符串格式
30 | ///
31 | /// 便于打印和阅读的字符串
32 | public override string ToString()
33 | {
34 | StringBuilder sb = new StringBuilder();
35 |
36 | sb.AppendFormat("code:{0}\n", Code);
37 | sb.AppendLine();
38 |
39 | if (Result != null)
40 | {
41 | sb.AppendLine("result:");
42 | sb.AppendFormat("code:{0}\n", Result.Code);
43 | if (!string.IsNullOrEmpty(Result.Error))
44 | {
45 | sb.AppendFormat("error:{0}\n", Result.Error);
46 | }
47 | if (Result.Data != null && Result.Data.Count > 0)
48 | {
49 | sb.AppendLine("log:");
50 | foreach (var key in Result.Data.Keys)
51 | {
52 | sb.AppendFormat("{0}:\n", key);
53 | foreach (var d in Result.Data)
54 | {
55 | if (d.Value != null)
56 | {
57 | sb.AppendFormat("Domain:{0}\n", d.Key);
58 | foreach (var s in d.Value)
59 | {
60 | if (s != null)
61 | {
62 | sb.AppendFormat("Name:{0}\nSize:{1}\nMtime:{2}\nUrl:{3}\n\n", s.Name, s.Size, s.Mtime, s.Url);
63 | }
64 | }
65 | }
66 | }
67 | sb.AppendLine();
68 | }
69 | }
70 | }
71 | else
72 | {
73 | if (!string.IsNullOrEmpty(Text))
74 | {
75 | sb.AppendLine("text:");
76 | sb.AppendLine(Text);
77 | }
78 | }
79 | sb.AppendLine();
80 |
81 | sb.AppendFormat("ref-code:{0}\n", RefCode);
82 |
83 | if (!string.IsNullOrEmpty(RefText))
84 | {
85 | sb.AppendLine("ref-text:");
86 | sb.AppendLine(RefText);
87 | }
88 |
89 | if (RefInfo != null)
90 | {
91 | sb.AppendFormat("ref-info:\n");
92 | foreach (var d in RefInfo)
93 | {
94 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value));
95 | }
96 | }
97 |
98 | return sb.ToString();
99 | }
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/UploadManager.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using Qiniu.Util;
3 | using Qiniu.Http;
4 | using System.Collections.Generic;
5 |
6 | namespace Qiniu.Storage
7 | {
8 | ///
9 | /// 上传管理器,根据文件/数据(流)大小以及阈值设置自动选择合适的上传方式
10 | ///
11 | public class UploadManager
12 | {
13 | private Config config;
14 |
15 | ///
16 | /// 初始化
17 | ///
18 | /// 文件上传的配置信息
19 | public UploadManager(Config config)
20 | {
21 | this.config = config;
22 | }
23 |
24 | ///
25 | /// 上传数据
26 | ///
27 | /// 待上传的数据
28 | /// 要保存的文件名称
29 | /// 上传凭证
30 | /// 上传可选设置
31 | /// 上传文件后的返回结果
32 | public HttpResult UploadData(byte[] data, string key, string token, PutExtra extra)
33 | {
34 | FormUploader formUploader = new FormUploader(this.config);
35 | return formUploader.UploadData(data, key, token, extra);
36 | }
37 |
38 | ///
39 | /// 上传文件,根据文件大小以及设置的阈值(用户初始化UploadManager时可指定该值)自动选择:
40 | /// 若文件大小超过设定阈值,使用ResumableUploader,否则使用FormUploader
41 | ///
42 | /// 本地待上传的文件名
43 | /// 要保存的文件名称
44 | /// 上传凭证
45 | /// 上传可选设置
46 | /// 上传文件后的返回结果
47 | public HttpResult UploadFile(string localFile, string key, string token, PutExtra extra)
48 | {
49 | HttpResult result = new HttpResult();
50 |
51 | System.IO.FileInfo fi = new System.IO.FileInfo(localFile);
52 | if (fi.Length > this.config.PutThreshold)
53 | {
54 | ResumableUploader resumeUploader = new ResumableUploader(config);
55 | result = resumeUploader.UploadFile(localFile, key, token, extra);
56 | }
57 | else
58 | {
59 | FormUploader formUploader = new FormUploader(config);
60 | result = formUploader.UploadFile(localFile, key, token, extra);
61 | }
62 |
63 | return result;
64 | }
65 |
66 |
67 | ///
68 | /// 上传文件数据流,根据文件大小以及设置的阈值(用户初始化UploadManager时可指定该值)自动选择:
69 | /// 若文件大小超过设定阈值,使用ResumableUploader,否则使用FormUploader
70 | ///
71 | /// 待上传的数据流
72 | /// 要保存的文件名称
73 | /// 上传凭证
74 | /// 上传可选设置
75 | /// 上传文件后的返回结果
76 | public HttpResult UploadStream(Stream stream, string key, string token, PutExtra extra)
77 | {
78 | HttpResult result = new HttpResult();
79 |
80 | if (stream.Length > this.config.PutThreshold)
81 | {
82 | ResumableUploader resumeUploader = new ResumableUploader(this.config);
83 | result = resumeUploader.UploadStream(stream, key, token, extra);
84 | }
85 | else
86 | {
87 | FormUploader formUploader = new FormUploader(this.config);
88 | result = formUploader.UploadStream(stream, key, token, extra);
89 | }
90 |
91 | return result;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/PfopInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace Qiniu.Storage
4 | {
5 | ///
6 | /// 持久化请求的回复
7 | ///
8 | public class PfopInfo
9 | {
10 | ///
11 | /// 任务ID
12 | ///
13 | [JsonProperty("id")]
14 | public string Id;
15 | ///
16 | /// 任务类型,为 1 代表为闲时任务
17 | ///
18 | [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
19 | public int? Type;
20 | ///
21 | /// 任务创建时间
22 | ///
23 | [JsonProperty("creationDate", NullValueHandling = NullValueHandling.Ignore)]
24 | public string CreationDate;
25 | ///
26 | /// 任务结果状态码
27 | ///
28 | [JsonProperty("code")]
29 | public int Code;
30 | ///
31 | /// 任务结果状态描述
32 | ///
33 | [JsonProperty("desc")]
34 | public string Desc;
35 | ///
36 | /// 待处理的数据文件
37 | ///
38 | [JsonProperty("inputKey")]
39 | public string InputKey;
40 | ///
41 | /// 待处理文件所在空间
42 | ///
43 | [JsonProperty("inputBucket")]
44 | public string InputBucket;
45 | ///
46 | /// 数据处理队列
47 | ///
48 | [JsonProperty("pipeline")]
49 | public string Pipeline;
50 | ///
51 | /// 任务的Reqid
52 | ///
53 | [JsonProperty("reqid")]
54 | public string Reqid;
55 | ///
56 | /// 任务来源
57 | ///
58 | [JsonProperty("taskFrom")]
59 | public string TaskFrom;
60 | ///
61 | /// 数据处理的命令集合
62 | ///
63 | [JsonProperty("items")]
64 | public PfopItems[] Items;
65 | }
66 |
67 | ///
68 | /// 持久化处理命令
69 | ///
70 | public class PfopItems
71 | {
72 | ///
73 | /// 命令
74 | ///
75 | [JsonProperty("cmd")]
76 | public string Cmd;
77 | ///
78 | /// 命令执行结果状态码
79 | ///
80 | [JsonProperty("code")]
81 | public string Code;
82 | ///
83 | /// 命令执行结果描述
84 | ///
85 | [JsonProperty("desc")]
86 | public string Desc;
87 | ///
88 | /// 命令执行错误
89 | ///
90 | [JsonProperty("Error", NullValueHandling = NullValueHandling.Ignore)]
91 | public string Error;
92 | ///
93 | /// VSample命令的生成文件名列表
94 | ///
95 | [JsonProperty("keys", NullValueHandling = NullValueHandling.Ignore)]
96 | public string[] Keys;
97 | ///
98 | /// 命令生成的文件名
99 | ///
100 | [JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)]
101 | public string Key;
102 | ///
103 | /// 命令生成的文件内容hash
104 | ///
105 | [JsonProperty("hash", NullValueHandling = NullValueHandling.Ignore)]
106 | public string Hash;
107 | ///
108 | /// 该命令是否返回了上一次相同命令生成的结果
109 | ///
110 | [JsonProperty("returnOld", NullValueHandling = NullValueHandling.Ignore)]
111 | public int? ReturnOld;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Qiniu/CDN/RefreshResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Newtonsoft.Json;
3 | using Qiniu.Http;
4 |
5 | namespace Qiniu.CDN
6 | {
7 | ///
8 | /// 缓存刷新-结果
9 | ///
10 | public class RefreshResult : HttpResult
11 | {
12 | ///
13 | /// 获取缓存刷新信息
14 | ///
15 | public RefreshInfo Result
16 | {
17 | get
18 | {
19 | RefreshInfo info = null;
20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text)))
21 | {
22 | info=JsonConvert.DeserializeObject(Text);
23 | }
24 | return info;
25 | }
26 | }
27 |
28 | ///
29 | /// 转换为易读字符串格式
30 | ///
31 | /// 便于打印和阅读的字符串
32 | public override string ToString()
33 | {
34 | StringBuilder sb = new StringBuilder();
35 |
36 | sb.AppendFormat("code:{0}\n", Code);
37 | sb.AppendLine();
38 |
39 | if (Result != null)
40 | {
41 | sb.AppendLine("result:");
42 | sb.AppendFormat("code:{0}\n", Result.Code);
43 | if (!string.IsNullOrEmpty(Result.Error))
44 | {
45 | sb.AppendFormat("error:{0}\n", Result.Error);
46 | }
47 | if (!string.IsNullOrEmpty(Result.RequestId))
48 | {
49 | sb.AppendFormat("requestId:{0}\n", Result.RequestId);
50 | }
51 | if (Result.InvalidDirs != null && Result.InvalidDirs.Count > 0)
52 | {
53 | sb.Append("invalidDirs:");
54 | foreach (var s in Result.InvalidDirs)
55 | {
56 | sb.Append(s + " ");
57 | }
58 | sb.AppendLine();
59 | }
60 | if (Result.InvalidUrls != null && Result.InvalidUrls.Count > 0)
61 | {
62 | sb.Append("invalidUrls:");
63 | foreach (var s in Result.InvalidUrls)
64 | {
65 | sb.Append(s + " ");
66 | }
67 | sb.AppendLine();
68 | }
69 | sb.AppendFormat("dirQuotaDay:{0}\n", Result.DirQuotaDay);
70 | sb.AppendFormat("dirSurplusDay:{0}\n", Result.DirSurplusDay);
71 | sb.AppendFormat("urlQuotaDay:{0}\n", Result.UrlQuotaDay);
72 | sb.AppendFormat("urlSurplusDay:{0}\n", Result.UrlSurplusDay);
73 | }
74 | else
75 | {
76 | if (!string.IsNullOrEmpty(Text))
77 | {
78 | sb.AppendLine("text:");
79 | sb.AppendLine(Text);
80 | }
81 | }
82 | sb.AppendLine();
83 |
84 | sb.AppendFormat("ref-code:{0}\n", RefCode);
85 |
86 | if (!string.IsNullOrEmpty(RefText))
87 | {
88 | sb.AppendLine("ref-text:");
89 | sb.AppendLine(RefText);
90 | }
91 |
92 | if (RefInfo != null)
93 | {
94 | sb.AppendFormat("ref-info:\n");
95 | foreach (var d in RefInfo)
96 | {
97 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value));
98 | }
99 | }
100 |
101 | return sb.ToString();
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/src/Qiniu/Util/UrlHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace Qiniu.Util
4 | {
5 | ///
6 | /// URL辅助工具(RegExp)
7 | ///
8 | public class UrlHelper
9 | {
10 | private static Regex regx = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?");
11 |
12 | private static Regex regu = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,/~\+#]*)?");
13 |
14 | private static Regex regd = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,/~\+#]*)?/");
15 |
16 | ///
17 | /// 是否合法URL
18 | ///
19 | /// 待判断的url
20 | ///
21 | public static bool IsValidUrl(string _url)
22 | {
23 | return regx.IsMatch(_url);
24 | }
25 |
26 | ///
27 | /// 是否一般URL(不包含?等后缀参数)
28 | ///
29 | /// 待判断的url
30 | ///
31 | public static bool IsNormalUrl(string _url)
32 | {
33 | return regu.IsMatch(_url);
34 | }
35 |
36 | ///
37 | /// 是否合法URL目录
38 | ///
39 | /// 待判断的url目录
40 | ///
41 | public static bool IsValidDir(string _dir)
42 | {
43 | return regd.IsMatch(_dir);
44 | }
45 |
46 | ///
47 | /// 从原始URL转换为一般URL(根据需要截断)
48 | ///
49 | /// 待转换的url
50 | ///
51 | public static string GetNormalUrl(string _url)
52 | {
53 | var m = regu.Match(_url);
54 | return m.Value;
55 | }
56 |
57 | ///
58 | /// URL分析,拆分出Host,Path,File,Query各个部分
59 | ///
60 | /// 原始URL
61 | /// host部分
62 | /// path部分
63 | /// 文件名
64 | /// 参数
65 | public static void UrlSplit(string url, out string host, out string path, out string file, out string query)
66 | {
67 | host = "";
68 | path = "";
69 | file = "";
70 | query = "";
71 |
72 | if(string.IsNullOrEmpty(url))
73 | {
74 | return;
75 | }
76 |
77 | int start = 0;
78 |
79 | try
80 | {
81 | Regex regHost = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+");
82 | host = regHost.Match(url, start).Value;
83 | start += host.Length;
84 |
85 | Regex regPath = new Regex(@"(/(\w|\-)*)+/");
86 | path = regPath.Match(url, start).Value;
87 | if (!string.IsNullOrEmpty(path))
88 | {
89 | start += path.Length;
90 | }
91 |
92 | int index = url.IndexOf('?', start);
93 | if (index > 0)
94 | {
95 | file = url.Substring(start, index - start);
96 | query = url.Substring(index);
97 | }
98 | else
99 | {
100 | file = url.Substring(start);
101 | query = "";
102 | }
103 | }
104 | catch(System.Exception)
105 | {
106 | //
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/QiniuTests/Http/Middleware.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using NUnit.Framework;
3 | using Qiniu.Http;
4 |
5 | namespace QiniuTests.Http
6 | {
7 | class RecorderMiddleware : IMiddleware
8 | {
9 | private readonly List _orderRecorder;
10 |
11 | private readonly string _label;
12 |
13 | public RecorderMiddleware(List orderRecorder, string label)
14 | {
15 | _orderRecorder = orderRecorder;
16 | _label = label;
17 | }
18 |
19 | public HttpResult Send(HttpRequestOptions req, DNextSend next)
20 | {
21 | _orderRecorder.Add("bef_" + _label + _orderRecorder.Count);
22 | HttpResult result = next(req);
23 | _orderRecorder.Add("aft_" + _label + _orderRecorder.Count);
24 | return result;
25 | }
26 | }
27 |
28 | [TestFixture]
29 | public class MiddlewareTests
30 | {
31 | [Test]
32 | public void SendWithMiddlewareTest()
33 | {
34 | HttpManager httpManager = new HttpManager(true);
35 |
36 | List orderRecorder = new List();
37 |
38 | List middlewares = new List
39 | {
40 | new RecorderMiddleware(orderRecorder, "A"),
41 | new RecorderMiddleware(orderRecorder, "B")
42 | };
43 |
44 | HttpResult resp = httpManager.Get("https://example.com/index.html", null, null, middlewares);
45 |
46 | Assert.AreEqual((int)HttpCode.OK, resp.Code, resp.ToString());
47 | CollectionAssert.AreEqual(
48 | new List
49 | {
50 | "bef_A0",
51 | "bef_B1",
52 | "aft_B2",
53 | "aft_A3"
54 | },
55 | orderRecorder
56 | );
57 | }
58 |
59 | [Test]
60 | public void RetryDomainsMiddlewareTest()
61 | {
62 |
63 | HttpManager httpManager = new HttpManager(true);
64 |
65 | List orderRecorder = new List();
66 |
67 | List middlewares = new List
68 | {
69 | new RetryDomainsMiddleware(
70 | new List
71 | {
72 | "unavailable.csharpsdk.qiniu.com",
73 | "example.com"
74 | },
75 | 3
76 | ),
77 | new RecorderMiddleware(orderRecorder, "A")
78 | };
79 |
80 | HttpResult resp = httpManager.Get("https://fake.csharpsdk.qiniu.com/index.html", null, null, middlewares);
81 |
82 | Assert.AreEqual((int)HttpCode.OK, resp.Code, resp.ToString());
83 |
84 | CollectionAssert.AreEqual(
85 | new List
86 | {
87 | // fake.csharpsdk.qiniu.com
88 | "bef_A0",
89 | "aft_A1",
90 | "bef_A2",
91 | "aft_A3",
92 | "bef_A4",
93 | "aft_A5",
94 | // unavailable.csharpsdk.qiniu.com
95 | "bef_A6",
96 | "aft_A7",
97 | "bef_A8",
98 | "aft_A9",
99 | "bef_A10",
100 | "aft_A11",
101 | // qiniu.com
102 | "bef_A12",
103 | "aft_A13"
104 | },
105 | orderRecorder
106 | );
107 | }
108 | }
109 | }
--------------------------------------------------------------------------------
/src/Qiniu/Storage/FileInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace Qiniu.Storage
4 | {
5 | ///
6 | /// 获取空间文件信息(stat操作)的有效内容
7 | ///
8 | public class FileInfo
9 | {
10 | ///
11 | /// 文件大小(字节)
12 | ///
13 | [JsonProperty("fsize")]
14 | public long Fsize { set; get; }
15 |
16 | ///
17 | /// 文件hash(ETAG)
18 | ///
19 | [JsonProperty("hash")]
20 | public string Hash { set; get; }
21 |
22 | ///
23 | /// 文件MIME类型
24 | ///
25 | [JsonProperty("mimeType")]
26 | public string MimeType { set; get; }
27 |
28 | ///
29 | /// 文件上传时间
30 | ///
31 | [JsonProperty("putTime")]
32 | public long PutTime { set; get; }
33 |
34 | ///
35 | /// 文件存储类型。
36 | /// 0 标准存储
37 | /// 1 低频存储
38 | /// 2 归档存储
39 | /// 3 深度归档存储
40 | /// 4 归档直读存储
41 | ///
42 | [JsonProperty("type")]
43 | public int FileType { get; set; }
44 |
45 | ///
46 | /// 文件解冻状态。
47 | /// 1 解冻中
48 | /// 2 已解冻
49 | /// 0 如果是归档/深度归档,但处于冻结,后端不返回此字段,因此默认值为 0。请勿依赖 0 判断冻结状态
50 | ///
51 | [JsonProperty("restoreStatus", NullValueHandling = NullValueHandling.Ignore)]
52 | public int RestoreStatus { get; set; }
53 |
54 | ///
55 | /// 文件的存储状态。
56 | /// 0 启用,非禁用后端不返回此字段,因此默认值为 0。
57 | /// 1 禁用
58 | ///
59 | [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)]
60 | public int Status { get; set; }
61 |
62 | ///
63 | /// 文件的 md5 值。
64 | /// 服务端不确保一定返回此字段,详见:
65 | /// https://developer.qiniu.com/kodo/1308/stat#:~:text=%E8%AF%A5%E5%AD%97%E6%AE%B5%E3%80%82-,md5,-%E5%90%A6
66 | ///
67 | [JsonProperty("md5", NullValueHandling = NullValueHandling.Ignore)]
68 | public string Md5 { get; set; }
69 |
70 | ///
71 | /// 文件过期删除日期,Unix 时间戳格式。
72 | /// 文件在设置过期时间后才会返回该字段。
73 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。
74 | ///
75 | [JsonProperty("expiration", NullValueHandling = NullValueHandling.Ignore)]
76 | public int Expiration { get; set; }
77 |
78 | ///
79 | /// 文件生命周期中转为低频存储的日期,Unix 时间戳格式。
80 | /// 文件在设置过期时间后才会返回该字段。
81 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。
82 | ///
83 | [JsonProperty("transitionToIA", NullValueHandling = NullValueHandling.Ignore)]
84 | public int TransitionToIa { get; set; }
85 |
86 | ///
87 | /// 文件生命周期中转为归档直读存储的日期,Unix 时间戳格式。
88 | /// 文件在设置过期时间后才会返回该字段。
89 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。
90 | ///
91 | [JsonProperty("transitionToArchiveIR", NullValueHandling = NullValueHandling.Ignore)]
92 | public int TransitionToArchiveIr { get; set; }
93 |
94 | ///
95 | /// 文件生命周期中转为归档存储的日期,Unix 时间戳格式。
96 | /// 文件在设置过期时间后才会返回该字段。
97 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。
98 | ///
99 | [JsonProperty("transitionToARCHIVE", NullValueHandling = NullValueHandling.Ignore)]
100 | public int TransitionToArchive { get; set; }
101 |
102 | ///
103 | /// 文件生命周期中转为深度归档存储的日期,Unix 时间戳格式。
104 | /// 文件在设置过期时间后才会返回该字段。
105 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。
106 | ///
107 | [JsonProperty("transitionToDeepArchive", NullValueHandling = NullValueHandling.Ignore)]
108 | public int TransitionToDeepArchive { get; set; }
109 | }
110 | }
--------------------------------------------------------------------------------
/src/Qiniu/Storage/Zone.cs:
--------------------------------------------------------------------------------
1 | namespace Qiniu.Storage
2 | {
3 | ///
4 | /// 目前已支持的区域:华东/华东2/华北/华南/北美/新加坡/首尔
5 | ///
6 | public class Zone
7 | {
8 | ///
9 | /// 资源管理
10 | ///
11 | public string RsHost { set; get; }
12 |
13 | ///
14 | /// 源列表
15 | ///
16 | public string RsfHost { set; get; }
17 |
18 | ///
19 | /// 数据处理
20 | ///
21 | public string ApiHost { set; get; }
22 |
23 | ///
24 | /// 镜像刷新、资源抓取
25 | ///
26 | public string IovipHost { set; get; }
27 |
28 | ///
29 | /// 资源上传
30 | ///
31 | public string[] SrcUpHosts { set; get; }
32 |
33 | ///
34 | /// CDN加速
35 | ///
36 | public string[] CdnUpHosts { set; get; }
37 |
38 | ///
39 | /// 华东
40 | ///
41 | public static Zone ZONE_CN_East = new Zone()
42 | {
43 | RsHost = "rs.qbox.me",
44 | RsfHost = "rsf.qbox.me",
45 | ApiHost = "api.qiniuapi.com",
46 | IovipHost = "iovip.qbox.me",
47 | SrcUpHosts = new string[] { "up.qiniup.com" },
48 | CdnUpHosts = new string[] { "upload.qiniup.com" }
49 | };
50 |
51 | ///
52 | /// 华东-浙江2
53 | ///
54 | public static Zone ZONE_CN_East_2 = new Zone()
55 | {
56 | RsHost = "rs-cn-east-2.qiniuapi.com",
57 | RsfHost = "rsf-cn-east-2.qiniuapi.com",
58 | ApiHost = "api-cn-east-2.qiniuapi.com",
59 | IovipHost = "iovip-cn-east-2.qiniuio.com",
60 | SrcUpHosts = new string[] { "up-cn-east-2.qiniup.com" },
61 | CdnUpHosts = new string[] { "upload-cn-east-2.qiniup.com" }
62 | };
63 |
64 | ///
65 | /// 华北
66 | ///
67 | public static Zone ZONE_CN_North = new Zone()
68 | {
69 | RsHost = "rs-z1.qbox.me",
70 | RsfHost = "rsf-z1.qbox.me",
71 | ApiHost = "api-z1.qiniuapi.com",
72 | IovipHost = "iovip-z1.qbox.me",
73 | SrcUpHosts = new string[] { "up-z1.qiniup.com" },
74 | CdnUpHosts = new string[] { "upload-z1.qiniup.com" }
75 | };
76 |
77 | ///
78 | /// 华南
79 | ///
80 | public static Zone ZONE_CN_South = new Zone()
81 | {
82 | RsHost = "rs-z2.qbox.me",
83 | RsfHost = "rsf-z2.qbox.me",
84 | ApiHost = "api-z2.qiniuapi.com",
85 | IovipHost = "iovip-z2.qbox.me",
86 | SrcUpHosts = new string[] { "up-z2.qiniup.com" },
87 | CdnUpHosts = new string[] { "upload-z2.qiniup.com" }
88 | };
89 |
90 | ///
91 | /// 北美
92 | ///
93 | public static Zone ZONE_US_North = new Zone()
94 | {
95 | RsHost = "rs-na0.qbox.me",
96 | RsfHost = "rsf-na0.qbox.me",
97 | ApiHost = "api-na0.qiniuapi.com",
98 | IovipHost = "iovip-na0.qbox.me",
99 | SrcUpHosts = new string[] { "up-na0.qiniup.com" },
100 | CdnUpHosts = new string[] { "upload-na0.qiniup.com" }
101 | };
102 |
103 | ///
104 | /// 新加坡
105 | ///
106 | public static Zone ZONE_AS_Singapore = new Zone()
107 | {
108 | RsHost = "rs-as0.qbox.me",
109 | RsfHost = "rsf-as0.qbox.me",
110 | ApiHost = "api-as0.qiniuapi.com",
111 | IovipHost = "iovip-as0.qbox.me",
112 | SrcUpHosts = new string[] { "up-as0.qiniup.com" },
113 | CdnUpHosts = new string[] { "upload-as0.qiniup.com" }
114 | };
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/QiniuTests/QiniuTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}
7 | Library
8 | Properties
9 | QiniuTests
10 | QiniuTests
11 | netcoreapp2.0
12 | 512
13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 10.0
15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
17 | False
18 | UnitTest
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}
35 | Qiniu
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | runtime; build; native; contentfiles; analyzers; buildtransitive
44 | all
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | False
55 |
56 |
57 | False
58 |
59 |
60 | False
61 |
62 |
63 | False
64 |
65 |
66 |
67 |
68 |
75 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/DownloadManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using Qiniu.Util;
5 | using Qiniu.Http;
6 |
7 | namespace Qiniu.Storage
8 | {
9 | ///
10 | /// 空间文件下载,只提供简单下载逻辑
11 | /// 对于大文件下载、断点续下载等需求,可以根据实际情况自行实现
12 | ///
13 | public class DownloadManager
14 | {
15 | ///
16 | /// 生成授权的下载链接(访问私有空间中的文件时需要使用这种链接)
17 | ///
18 | /// 账号(密钥)
19 | /// (私有)空间文件的下载域名
20 | /// (私有)空间文件名
21 | /// 从生成此链接的时刻算起,该链接有效时间(单位:秒)
22 | /// 已授权的下载链接
23 | public static string CreatePrivateUrl(Mac mac, string domain, string fileName, int expireInSeconds = 3600)
24 | {
25 | long deadline = UnixTimestamp.GetUnixTimestamp(expireInSeconds);
26 | string publicUrl = CreatePublishUrl(domain, fileName);
27 | StringBuilder sb = new StringBuilder(publicUrl);
28 | if (publicUrl.Contains("?"))
29 | {
30 | sb.AppendFormat("&e={0}", deadline);
31 | }
32 | else
33 | {
34 | sb.AppendFormat("?e={0}", deadline);
35 | }
36 |
37 | string token = Auth.CreateDownloadToken(mac, sb.ToString());
38 | sb.AppendFormat("&token={0}", token);
39 |
40 | return sb.ToString();
41 | }
42 |
43 | ///
44 | /// 生成公开空间的下载链接
45 | ///
46 | /// 公开空间的文件下载域名
47 | /// 公开空间文件名
48 | /// 公开空间文件下载链接
49 | public static string CreatePublishUrl(string domain, string fileName)
50 | {
51 | return string.Format("{0}/{1}", domain, Uri.EscapeUriString(fileName));
52 | }
53 |
54 | ///
55 | /// 下载文件到本地
56 | ///
57 | /// (可访问的或者已授权的)链接
58 | /// (另存为)本地文件名
59 | /// 下载资源的结果
60 | public static HttpResult Download(string url, string saveasFile)
61 | {
62 | HttpResult result = new HttpResult();
63 |
64 | try
65 | {
66 | HttpManager httpManager = new HttpManager();
67 |
68 | result = httpManager.Get(url, null, true);
69 | if (result.Code == (int)HttpCode.OK)
70 | {
71 | using (FileStream fs = File.Create(saveasFile, result.Data.Length))
72 | {
73 | fs.Write(result.Data, 0, result.Data.Length);
74 | fs.Flush();
75 | }
76 | result.RefText += string.Format("[{0}] [Download] Success: (Remote file) ==> \"{1}\"\n",
77 | DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), saveasFile);
78 | }
79 | else
80 | {
81 | result.RefText += string.Format("[{0}] [Download] Error: code = {1}\n",
82 | DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), result.Code);
83 | }
84 | }
85 | catch (Exception ex)
86 | {
87 | StringBuilder sb = new StringBuilder();
88 | sb.AppendFormat("[{0}] [Download] Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"));
89 | Exception e = ex;
90 | while (e != null)
91 | {
92 | sb.Append(e.Message + " ");
93 | e = e.InnerException;
94 | }
95 | sb.AppendLine();
96 |
97 | result.RefCode = (int)HttpCode.USER_UNDEF;
98 | result.RefText += sb.ToString();
99 | }
100 |
101 | return result;
102 | }
103 |
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/Qiniu/Util/CRC32.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Qiniu.Util
5 | {
6 | ///
7 | /// CRC32计算器
8 | ///
9 | public class CRC32
10 | {
11 | ///
12 | /// magic
13 | ///
14 | public const uint IEEE = 0xedb88320;
15 | private uint[] Table;
16 | private uint Value;
17 |
18 | ///
19 | /// 初始化
20 | ///
21 | public CRC32()
22 | {
23 | Value = 0;
24 | Table = makeTable(IEEE);
25 | }
26 |
27 | ///
28 | /// 写入
29 | ///
30 | /// 字节数据
31 | /// 偏移位置
32 | /// 字节数
33 | public void Write(byte[] p, int offset, int count)
34 | {
35 | this.Value = Update(this.Value, this.Table, p, offset, count);
36 | }
37 |
38 | ///
39 | /// 校验和
40 | ///
41 | /// 校验和
42 | public uint Sum()
43 | {
44 | return this.Value;
45 | }
46 |
47 | private static uint[] makeTable(uint poly)
48 | {
49 | uint[] table = new uint[256];
50 | for (int i = 0; i < 256; i++)
51 | {
52 | uint crc = (uint)i;
53 | for (int j = 0; j < 8; j++)
54 | {
55 | if ((crc & 1) == 1)
56 | crc = (crc >> 1) ^ poly;
57 | else
58 | crc >>= 1;
59 | }
60 | table[i] = crc;
61 | }
62 | return table;
63 | }
64 |
65 | ///
66 | /// 更新
67 | ///
68 | /// crc32
69 | /// 表
70 | /// 字节数据
71 | /// 偏移位置
72 | /// 字节数
73 | ///
74 | public static uint Update(UInt32 crc, UInt32[] table, byte[] p, int offset, int count)
75 | {
76 | crc = ~crc;
77 | for (int i = 0; i < count; i++)
78 | {
79 | crc = table[((byte)crc) ^ p[offset + i]] ^ (crc >> 8);
80 | }
81 | return ~crc;
82 | }
83 |
84 | ///
85 | /// 计算字节数据的crc32值
86 | ///
87 | /// 二进制数据
88 | /// crc32值
89 | public static uint CheckSumBytes(byte[] data)
90 | {
91 | CRC32 crc = new CRC32();
92 | crc.Write(data, 0, data.Length);
93 | return crc.Sum();
94 | }
95 |
96 | ///
97 | /// 检验
98 | ///
99 | /// 字节数据
100 | /// 偏移位置
101 | /// 字节数
102 | ///
103 | public static uint CheckSumSlice(byte[] data, int offset, int count)
104 | {
105 | CRC32 crc = new CRC32();
106 | crc.Write(data, offset, count);
107 | return crc.Sum();
108 | }
109 |
110 | ///
111 | /// 计算沙盒文件的crc32值
112 | ///
113 | /// 沙盒文件全路径
114 | /// crc32值
115 | public static uint checkSumFile(string filePath)
116 | {
117 | CRC32 crc = new CRC32();
118 | int bufferLen = 32 * 1024;
119 | using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
120 | {
121 | byte[] buffer = new byte[bufferLen];
122 | while (true)
123 | {
124 | int n = fs.Read(buffer, 0, bufferLen);
125 | if (n == 0)
126 | break;
127 | crc.Write(buffer, 0, n);
128 | }
129 | }
130 | return crc.Sum();
131 | }
132 | }
133 | }
--------------------------------------------------------------------------------
/src/Qiniu/Http/HttpCode.cs:
--------------------------------------------------------------------------------
1 | namespace Qiniu.Http
2 | {
3 | ///
4 | /// HTTP 状态码
5 | ///
6 | public enum HttpCode
7 | {
8 | #region _PRE_
9 |
10 | ///
11 | /// 成功
12 | ///
13 | OK = 200,
14 |
15 | ///
16 | /// 部分OK
17 | ///
18 | PARTLY_OK = 298,
19 |
20 | ///
21 | /// 请求错误
22 | ///
23 | BAD_REQUEST = 400,
24 |
25 | ///
26 | /// 认证授权失败
27 | ///
28 | AUTHENTICATION_FAILED = 401,
29 |
30 | ///
31 | /// 拒绝访问
32 | ///
33 | ACCESS_DENIED = 403,
34 |
35 | ///
36 | /// 资源不存在
37 | ///
38 | OBJECT_NOT_FOUND = 404,
39 |
40 | ///
41 | /// CRC32校验失败
42 | ///
43 | CRC32_CHECK_FAILEd = 406,
44 |
45 | ///
46 | /// 上传文件大小超限
47 | ///
48 | FILE_SIZE_EXCEED = 413,
49 |
50 | ///
51 | /// 镜像回源失败
52 | ///
53 | PREFETCH_FAILED = 478,
54 |
55 | ///
56 | /// 接口未实现
57 | ///
58 | NOT_IMPLEMENTED = 501,
59 |
60 | ///
61 | /// 错误网关
62 | ///
63 | BAD_GATEWAY = 502,
64 |
65 | ///
66 | /// 服务端不可用
67 | ///
68 | SERVER_UNAVAILABLE = 503,
69 |
70 | ///
71 | /// 服务端操作超时
72 | ///
73 | SERVER_TIME_EXCEED = 504,
74 |
75 | ///
76 | /// 超出带宽限制
77 | ///
78 | BANDWIDTH_LIMIT_EXCEEDED = 509,
79 |
80 | ///
81 | /// 单个资源访问频率过高
82 | ///
83 | TOO_FREQUENT_ACCESS = 573,
84 |
85 | ///
86 | /// 回调失败
87 | ///
88 | CALLBACK_FAILED = 579,
89 |
90 | ///
91 | /// 服务端操作失败
92 | ///
93 | SERVER_OPERATION_FAILED = 599,
94 |
95 | ///
96 | /// 资源内容被修改
97 | ///
98 | CONTENT_MODIFIED = 608,
99 |
100 | ///
101 | /// 文件不存在/指定资源不存在或已被删除
102 | ///
103 | FILE_NOT_EXIST = 612,
104 |
105 | ///
106 | /// 文件已存在/目标资源已存在
107 | ///
108 | FILE_EXISTS = 614,
109 |
110 | ///
111 | /// 当前操作无法在共享空间执行(被共享的用户进行操作时)
112 | ///
113 | INVALID_SHARE_BUCKET = 616,
114 |
115 | ///
116 | /// 当前操作无法在共享空间执行(所有者进行操作时)
117 | ///
118 | BUCKET_IS_SHARING = 618,
119 |
120 | ///
121 | /// 空间数量已达上限
122 | ///
123 | BUCKET_COUNT_LIMIT = 630,
124 |
125 | ///
126 | /// 空间或者文件不存在
127 | ///
128 | BUCKET_NOT_EXIST = 631,
129 |
130 | ///
131 | /// 共享空间达到上限
132 | ///
133 | EXCEED_SHARED_BUCKETS_LIMIT = 632,
134 |
135 | ///
136 | /// 列举资源(list)使用了非法的marker
137 | ///
138 | INVALID_MARKER = 640,
139 |
140 | ///
141 | /// 在断点续上传过程中,后续上传接收地址不正确或ctx信息已过期。
142 | ///
143 | CONTEXT_EXPIRED = 701,
144 |
145 | #endregion _PRE_
146 |
147 | #region _USR_
148 |
149 | ///
150 | /// 自定义HTTP状态码 (默认值)
151 | ///
152 | USER_UNDEF = 0,
153 |
154 | ///
155 | /// 自定义HTTP状态码 (用户取消)
156 | ///
157 | USER_CANCELED = -2,
158 |
159 | ///
160 | /// 自定义HTTP状态码 (用户暂停)
161 | ///
162 | USER_PAUSED = 1,
163 |
164 | ///
165 | /// 自定义HTTP状态码 (用户继续)
166 | ///
167 | USER_RESUMED = 2,
168 |
169 | ///
170 | /// 自定义HTTP状态码 (需要重试)
171 | ///
172 | USER_NEED_RETRY = 3,
173 |
174 | ///
175 | /// 自定义HTTP状态码 (异常或错误)
176 | ///
177 | INVALID_ARGUMENT = -4,
178 |
179 | ///
180 | /// 自定义HTTP状态码(文件不合法)
181 | ///
182 | INVALID_FILE = -3,
183 |
184 | ///
185 | /// 自定义HTTP状态码(凭证不合法)
186 | ///
187 | INVALID_TOKEN = -5,
188 |
189 | #endregion _USR_
190 |
191 | }
192 |
193 | }
194 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/BatchInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | namespace Qiniu.Storage
3 | {
4 | ///
5 | /// 批量处理返回的信息
6 | ///
7 | public class BatchInfo
8 | {
9 | ///
10 | /// 状态码
11 | ///
12 | [JsonProperty("code",NullValueHandling=NullValueHandling.Ignore)]
13 | public int Code { get; set; }
14 |
15 | ///
16 | /// 消息
17 | ///
18 | [JsonProperty("data",NullValueHandling=NullValueHandling.Ignore)]
19 | public BatchData Data { get; set; }
20 | }
21 |
22 | ///
23 | /// 批量处理的结果内容
24 | ///
25 | public class BatchData
26 | {
27 | ///
28 | /// 处理遇到的错误信息
29 | ///
30 | [JsonProperty("error", NullValueHandling = NullValueHandling.Ignore)]
31 | public string Error { get; set; }
32 |
33 | ///
34 | /// 文件hash(ETAG)
35 | ///
36 | [JsonProperty("hash", NullValueHandling = NullValueHandling.Ignore)]
37 | public string Hash { get; set; }
38 |
39 | ///
40 | /// 文件大小(字节)
41 | ///
42 | [JsonProperty("fsize", NullValueHandling = NullValueHandling.Ignore)]
43 | public long Fsize { get; set; }
44 |
45 | ///
46 | /// 文件MIME类型
47 | ///
48 | [JsonProperty("mimeType", NullValueHandling = NullValueHandling.Ignore)]
49 | public string MimeType { get; set; }
50 |
51 | ///
52 | /// 上传时间
53 | ///
54 | [JsonProperty("putTime", NullValueHandling = NullValueHandling.Ignore)]
55 | public long PutTime { get; set; }
56 |
57 | ///
58 | /// 文件存储类型。
59 | /// 0 标准存储
60 | /// 1 低频存储
61 | /// 2 归档存储
62 | /// 3 深度归档存储
63 | /// 4 归档直读存储
64 | ///
65 | [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
66 | public int FileType { get; set; }
67 |
68 | ///
69 | /// 文件解冻状态。
70 | /// 1 解冻中
71 | /// 2 已解冻
72 | /// 0 如果是归档/深度归档,但处于冻结,后端不返回此字段,因此默认值为 0。请勿依赖 0 判断冻结状态
73 | ///
74 | [JsonProperty("restoreStatus", NullValueHandling = NullValueHandling.Ignore)]
75 | public int RestoreStatus { get; set; }
76 |
77 | ///
78 | /// 文件的存储状态。
79 | /// 0 启用,非禁用后端不返回此字段,因此默认值为 0。
80 | /// 1 禁用
81 | ///
82 | [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)]
83 | public int Status { get; set; }
84 |
85 | ///
86 | /// 文件的 md5 值。
87 | /// 服务端不确保一定返回此字段,详见:
88 | /// https://developer.qiniu.com/kodo/1308/stat#:~:text=%E8%AF%A5%E5%AD%97%E6%AE%B5%E3%80%82-,md5,-%E5%90%A6
89 | ///
90 | [JsonProperty("md5", NullValueHandling = NullValueHandling.Ignore)]
91 | public string Md5 { get; set; }
92 |
93 | ///
94 | /// 文件过期删除日期,Unix 时间戳格式。
95 | /// 文件在设置过期时间后才会返回该字段。
96 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。
97 | ///
98 | [JsonProperty("expiration", NullValueHandling = NullValueHandling.Ignore)]
99 | public int Expiration { get; set; }
100 |
101 | ///
102 | /// 文件生命周期中转为低频存储的日期,Unix 时间戳格式。
103 | /// 文件在设置低频存储转换时间后才会返回该字段。
104 | /// 历史文件到期仍会自动转换,但不会返回该字段,重新设置文件转换时间可使历史文件返回该字段。
105 | ///
106 | [JsonProperty("TransitionToIA", NullValueHandling = NullValueHandling.Ignore)]
107 | public int TransitionToIa { get; set; }
108 |
109 | ///
110 | /// 文件生命周期中转为归档直读存储的日期,Unix 时间戳格式。
111 | /// 文件在设置归档直读存储转换时间后才会返回该字段。
112 | /// 历史文件到期仍会自动转换,但不会返回该字段,重新设置文件转换时间可使历史文件返回该字段。
113 | ///
114 | [JsonProperty("transitionToArchiveIR", NullValueHandling = NullValueHandling.Ignore)]
115 | public int TransitionToArchiveIr { get; set; }
116 |
117 | ///
118 | /// 文件生命周期中转为归档存储的日期,Unix 时间戳格式。
119 | /// 文件在设置归档存储转换时间后才会返回该字段。
120 | /// 历史文件到期仍会自动转换,但不会返回该字段,重新设置文件转换时间可使历史文件返回该字段。
121 | ///
122 | [JsonProperty("transitionToARCHIVE", NullValueHandling = NullValueHandling.Ignore)]
123 | public int TransitionToArchive { get; set; }
124 |
125 | ///
126 | /// 文件生命周期中转为深度归档存储的日期,Unix 时间戳格式。
127 | /// 文件在设置深度归档存储转换时间后才会返回该字段。
128 | /// 历史文件到期仍会自动转换,但不会返回该字段,重新设置文件转换时间可使历史文件返回该字段。
129 | ///
130 | [JsonProperty("transitionToDeepArchive", NullValueHandling = NullValueHandling.Ignore)]
131 | public int TransitionToDeepArchive { get; set; }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/QiniuTests/Storage/OperationManagerTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System;
3 | using System.Collections;
4 | using System.Text;
5 | using Qiniu.Util;
6 | using Qiniu.Http;
7 | using Qiniu.Tests;
8 |
9 | namespace Qiniu.Storage.Tests
10 | {
11 | [TestFixture]
12 | public class OperationManagerTests : TestEnv
13 | {
14 | private OperationManager getOperationManager()
15 | {
16 | Mac mac = new Mac(AccessKey, SecretKey);
17 | Config config = new Config();
18 | config.UseHttps = true;
19 |
20 | OperationManager manager = new OperationManager(mac, config);
21 | return manager;
22 | }
23 |
24 | [Test]
25 | public void PfopAndPrefopTest()
26 | {
27 | string key = "qiniu.mp4";
28 | bool force = true;
29 | string pipeline = "sdktest";
30 | string notifyUrl = "http://api.example.com/qiniu/pfop/notify";
31 | string saveMp4Entry = Base64.UrlSafeBase64Encode(Bucket + ":avthumb_test_target.mp4");
32 | string saveJpgEntry = Base64.UrlSafeBase64Encode(Bucket + ":vframe_test_target.jpg");
33 | string avthumbMp4Fop = "avthumb/mp4|saveas/" + saveMp4Entry;
34 | string vframeJpgFop = "vframe/jpg/offset/1|saveas/" + saveJpgEntry;
35 | string fops = string.Join(";", new string[] { avthumbMp4Fop, vframeJpgFop });
36 |
37 | OperationManager manager = getOperationManager();
38 | PfopResult pfopRet = manager.Pfop(Bucket, key, fops, pipeline, notifyUrl, force);
39 | if (pfopRet.Code != (int)HttpCode.OK)
40 | {
41 | Assert.Fail("pfop error: " + pfopRet.ToString());
42 | }
43 | Console.WriteLine(pfopRet.PersistentId);
44 |
45 | PrefopResult ret = manager.Prefop(pfopRet.PersistentId);
46 | if (ret.Code != (int)HttpCode.OK)
47 | {
48 | Assert.Fail("prefop error: " + ret.ToString());
49 | }
50 | Console.WriteLine(ret.ToString());
51 | }
52 |
53 | public static IEnumerable PfopOptionsTestCases
54 | {
55 | get
56 | {
57 | yield return new TestCaseData(
58 | 0, // type
59 | null // workflow template id
60 | );
61 | yield return new TestCaseData(
62 | 1,
63 | null
64 | );
65 | yield return new TestCaseData(
66 | 0,
67 | "test-workflow"
68 | );
69 | }
70 | }
71 |
72 | [TestCaseSource(typeof(OperationManagerTests), nameof(PfopOptionsTestCases))]
73 | public void PfopWithOptionsTest(int type, string workflowId)
74 | {
75 | string bucketName = Bucket;
76 | string key = "qiniu.mp4";
77 |
78 | StringBuilder persistentKeyBuilder = new StringBuilder("test-pfop/test-pfop-by-api");
79 | if (type > 0)
80 | {
81 | persistentKeyBuilder.Append("type_" + type);
82 | }
83 |
84 | string fops;
85 | if (!string.IsNullOrEmpty(workflowId))
86 | {
87 | fops = null;
88 | }
89 | else
90 | {
91 | string saveEntry = Base64.UrlSafeBase64Encode(String.Join(
92 | ":",
93 | bucketName,
94 | persistentKeyBuilder.ToString()
95 | ));
96 | fops = "avinfo|saveas/" + saveEntry;
97 | }
98 |
99 | OperationManager manager = getOperationManager();
100 | PfopResult pfopRet = manager.Pfop(
101 | Bucket,
102 | key,
103 | fops,
104 | null,
105 | null,
106 | true,
107 | type,
108 | workflowId
109 | );
110 | if (pfopRet.Code != (int)HttpCode.OK)
111 | {
112 | Assert.Fail("pfop error: " + pfopRet);
113 | }
114 |
115 | PrefopResult prefopRet = manager.Prefop(pfopRet.PersistentId);
116 | if (prefopRet.Code != (int)HttpCode.OK)
117 | {
118 | Assert.Fail("prefop error: " + prefopRet);
119 | }
120 |
121 | Assert.IsNotNull(prefopRet.Result.CreationDate);
122 | Assert.IsNotEmpty(prefopRet.Result.CreationDate);
123 |
124 | if (type == 1)
125 | {
126 | Assert.AreEqual(1, prefopRet.Result.Type);
127 | }
128 |
129 | if (!string.IsNullOrEmpty(workflowId))
130 | {
131 | Assert.IsNotNull(prefopRet.Result.TaskFrom);
132 | Assert.IsNotEmpty(prefopRet.Result.TaskFrom);
133 | Assert.IsTrue(prefopRet.Result.TaskFrom.Contains(workflowId));
134 | }
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/Qiniu/Util/StringHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Qiniu.Util
6 | {
7 | ///
8 | /// 字符串处理工具
9 | ///
10 | public class StringHelper
11 | {
12 | ///
13 | /// URL编码
14 | ///
15 | /// 源字符串
16 | /// URL编码字符串
17 | public static string UrlEncode(string text)
18 | {
19 | return Uri.EscapeDataString(text);
20 | }
21 |
22 | ///
23 | /// URL键值对编码
24 | ///
25 | /// 键值对
26 | /// URL编码的键值对数据
27 | public static string UrlFormEncode(Dictionary values)
28 | {
29 | StringBuilder urlValuesBuilder = new StringBuilder();
30 |
31 | foreach (KeyValuePair kvp in values)
32 | {
33 | urlValuesBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(kvp.Key),
34 | Uri.EscapeDataString(kvp.Value));
35 | }
36 |
37 | string encodedStr = urlValuesBuilder.ToString();
38 | return encodedStr.Substring(0, encodedStr.Length - 1);
39 | }
40 |
41 | ///
42 | /// 合法 Header 字段名的字符
43 | ///
44 | private static readonly Dictionary ValidMimeHeaderNameTokens = new Dictionary
45 | {
46 | {'!', true},
47 | {'#', true},
48 | {'$', true},
49 | {'%', true},
50 | {'&', true},
51 | {'\\', true},
52 | {'*', true},
53 | {'+', true},
54 | {'-', true},
55 | {'.', true},
56 |
57 | {'0', true},
58 | {'1', true},
59 | {'2', true},
60 | {'3', true},
61 | {'4', true},
62 | {'5', true},
63 | {'6', true},
64 | {'7', true},
65 | {'8', true},
66 | {'9', true},
67 |
68 | {'A', true},
69 | {'B', true},
70 | {'C', true},
71 | {'D', true},
72 | {'E', true},
73 | {'F', true},
74 | {'G', true},
75 | {'H', true},
76 | {'I', true},
77 | {'J', true},
78 | {'K', true},
79 | {'L', true},
80 | {'M', true},
81 | {'N', true},
82 | {'O', true},
83 | {'P', true},
84 | {'Q', true},
85 | {'R', true},
86 | {'S', true},
87 | {'T', true},
88 | {'U', true},
89 | {'W', true},
90 | {'V', true},
91 | {'X', true},
92 | {'Y', true},
93 | {'Z', true},
94 |
95 | {'^', true},
96 | {'_', true},
97 | {'`', true},
98 |
99 | {'a', true},
100 | {'b', true},
101 | {'c', true},
102 | {'d', true},
103 | {'e', true},
104 | {'f', true},
105 | {'g', true},
106 | {'h', true},
107 | {'i', true},
108 | {'j', true},
109 | {'k', true},
110 | {'l', true},
111 | {'m', true},
112 | {'n', true},
113 | {'o', true},
114 | {'p', true},
115 | {'q', true},
116 | {'r', true},
117 | {'s', true},
118 | {'t', true},
119 | {'u', true},
120 | {'v', true},
121 | {'w', true},
122 | {'x', true},
123 | {'y', true},
124 | {'z', true},
125 | {'|', true},
126 | {'~', true}
127 | };
128 |
129 | ///
130 | /// 是否合法的 Header 字段名
131 | ///
132 | /// Header 字段名
133 | /// 是否合法
134 | private static bool IsValidMimeHeaderName(string fieldName)
135 | {
136 | foreach (var ch in fieldName)
137 | {
138 | if (!ValidMimeHeaderNameTokens.ContainsKey(ch))
139 | {
140 | return false;
141 | }
142 | }
143 |
144 | return true;
145 | }
146 |
147 | ///
148 | /// 规范化 Header 字段名
149 | /// 将合法的字段名规范化为 Abc-Xyz 这种首字母大写的形式
150 | ///
151 | /// Header 字段名
152 | /// 规范化后的 Header 字段名
153 | public static string CanonicalMimeHeaderKey(string fieldName)
154 | {
155 | if (!IsValidMimeHeaderName(fieldName))
156 | {
157 | return fieldName;
158 | }
159 |
160 | string result = "";
161 | bool upper = true;
162 | foreach (var ch in fieldName)
163 | {
164 | switch (upper)
165 | {
166 | case true when 'a' <= ch && ch <= 'z':
167 | result += char.ToUpper(ch);
168 | break;
169 | case false when 'A' <= ch && ch <= 'Z':
170 | result += char.ToLower(ch);
171 | break;
172 | default:
173 | result += ch;
174 | break;
175 | }
176 |
177 | upper = ch == '-';
178 | }
179 | return result;
180 | }
181 | }
182 | }
--------------------------------------------------------------------------------
/src/Qiniu/Http/HttpResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text;
4 |
5 | namespace Qiniu.Http
6 | {
7 | ///
8 | /// HTTP请求(GET,POST等)的返回消息
9 | ///
10 | public class HttpResult
11 | {
12 | private static readonly IReadOnlyList NotRetryableHttpCodes = new List
13 | {
14 | (int)HttpCode.INVALID_ARGUMENT,
15 | (int)HttpCode.INVALID_FILE,
16 | (int)HttpCode.INVALID_TOKEN,
17 | (int)HttpCode.USER_CANCELED,
18 | (int)HttpCode.USER_PAUSED,
19 | // 服务端
20 | (int)HttpCode.NOT_IMPLEMENTED,
21 | (int)HttpCode.BANDWIDTH_LIMIT_EXCEEDED,
22 | (int)HttpCode.TOO_FREQUENT_ACCESS,
23 | (int)HttpCode.CALLBACK_FAILED,
24 | (int)HttpCode.CONTENT_MODIFIED,
25 | (int)HttpCode.FILE_NOT_EXIST,
26 | (int)HttpCode.FILE_EXISTS,
27 | (int)HttpCode.INVALID_SHARE_BUCKET,
28 | (int)HttpCode.BUCKET_IS_SHARING,
29 | (int)HttpCode.BUCKET_COUNT_LIMIT,
30 | (int)HttpCode.BUCKET_NOT_EXIST,
31 | (int)HttpCode.EXCEED_SHARED_BUCKETS_LIMIT,
32 | (int)HttpCode.INVALID_MARKER,
33 | (int)HttpCode.CONTEXT_EXPIRED
34 | };
35 |
36 | ///
37 | /// 状态码 (200表示OK)
38 | ///
39 | public int Code { get; set; }
40 |
41 | ///
42 | /// 消息或错误文本
43 | ///
44 | public string Text { get; set; }
45 |
46 | ///
47 | /// 消息或错误(二进制格式)
48 | ///
49 | public byte[] Data { get; set; }
50 |
51 | ///
52 | /// 参考代码(用户自定义)
53 | ///
54 | public int RefCode { get; set; }
55 |
56 | ///
57 | /// 附加信息(用户自定义,如Exception内容)
58 | ///
59 | public string RefText { get; set; }
60 |
61 | ///
62 | /// 参考信息(从返回消息WebResponse的头部获取)
63 | ///
64 | public Dictionary RefInfo { get; set; }
65 |
66 | ///
67 | /// 初始化(所有成员默认值,需要后续赋值)
68 | ///
69 | public HttpResult()
70 | {
71 | Code = (int)HttpCode.USER_UNDEF;
72 | Text = null;
73 | Data = null;
74 |
75 | RefCode = (int)HttpCode.USER_UNDEF;
76 | RefInfo = null;
77 | }
78 |
79 | ///
80 | /// 对象复制
81 | ///
82 | /// 要复制其内容的来源
83 | public void Shadow(HttpResult hr)
84 | {
85 | this.Code = hr.Code;
86 | this.Text = hr.Text;
87 | this.Data = hr.Data;
88 | this.RefCode = hr.RefCode;
89 | this.RefText += hr.RefText;
90 | this.RefInfo = hr.RefInfo;
91 | }
92 |
93 | ///
94 | /// 转换为易读或便于打印的字符串格式
95 | ///
96 | /// 便于打印和阅读的字符串
97 | public override string ToString()
98 | {
99 | StringBuilder sb = new StringBuilder();
100 |
101 | sb.AppendFormat("code:{0}", Code);
102 | sb.AppendLine();
103 |
104 | if (!string.IsNullOrEmpty(Text))
105 | {
106 | sb.AppendLine("text:");
107 | sb.AppendLine(Text);
108 | }
109 |
110 | if (Data != null)
111 | {
112 | sb.AppendLine("data:");
113 | int n = 1024;
114 | if (Data.Length <= n)
115 | {
116 | sb.AppendLine(Encoding.UTF8.GetString(Data));
117 | }
118 | else
119 | {
120 |
121 | sb.AppendLine(Encoding.UTF8.GetString(Data, 0, n));
122 | sb.AppendFormat("<--- TOO-LARGE-TO-DISPLAY --- TOTAL {0} BYTES --->", Data.Length);
123 | sb.AppendLine();
124 | }
125 | }
126 |
127 | sb.AppendLine();
128 |
129 | sb.AppendFormat("ref-code:{0}", RefCode);
130 | sb.AppendLine();
131 |
132 | if(!string.IsNullOrEmpty(RefText))
133 | {
134 | sb.AppendLine("ref-text:");
135 | sb.AppendLine(RefText);
136 | }
137 |
138 | if (RefInfo != null)
139 | {
140 | sb.AppendLine("ref-info:");
141 | foreach (var d in RefInfo)
142 | {
143 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value));
144 | }
145 | }
146 |
147 | sb.AppendLine();
148 |
149 | return sb.ToString();
150 | }
151 |
152 | ///
153 | /// 非法上传凭证错误
154 | ///
155 | public static HttpResult InvalidToken = new HttpResult {
156 | Code=(int)HttpCode.INVALID_TOKEN,
157 | Text="invalid uptoken"
158 | };
159 |
160 | ///
161 | /// 非法文件错误
162 | ///
163 | public static HttpResult InvalidFile = new HttpResult
164 | {
165 | Code = (int)HttpCode.INVALID_FILE,
166 | Text = "invalid file"
167 | };
168 |
169 | public bool NeedRetry()
170 | {
171 | if (Code > 0 && Code < 500)
172 | {
173 | return false;
174 | }
175 | if (NotRetryableHttpCodes.Contains(Code))
176 | {
177 | return false;
178 | }
179 | return true;
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/Qiniu/Storage/ZoneHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Collections.Generic;
4 | using Qiniu.Http;
5 | using Newtonsoft.Json;
6 |
7 | namespace Qiniu.Storage
8 | {
9 | internal class ZoneCacheValue
10 | {
11 | public DateTime Deadline { get; set; }
12 | public Zone Zone { get; set; }
13 | }
14 |
15 | ///
16 | /// Zone辅助类,查询及配置Zone
17 | ///
18 | public class ZoneHelper
19 | {
20 | private static Dictionary zoneCache = new Dictionary();
21 | private static object rwLock = new object();
22 |
23 | ///
24 | /// 从 UC 服务查询得到各个服务域名,生成 Zone 对象并返回
25 | ///
26 | /// AccessKey
27 | /// 空间名称
28 | /// 查询域名
29 | /// 备用查询域名
30 | public static Zone QueryZone(
31 | string accessKey,
32 | string bucket,
33 | string ucHost = null,
34 | List backupUcHosts = null
35 | )
36 | {
37 | ZoneCacheValue zoneCacheValue = null;
38 | string cacheKey = string.Format("{0}:{1}", accessKey, bucket);
39 |
40 | //check from cache
41 | lock (rwLock)
42 | {
43 | if (zoneCache.ContainsKey(cacheKey))
44 | {
45 | zoneCacheValue = zoneCache[cacheKey];
46 | }
47 | }
48 |
49 | if (
50 | zoneCacheValue != null &&
51 | DateTime.Now < zoneCacheValue.Deadline &&
52 | zoneCacheValue.Zone != null
53 | )
54 | {
55 | return zoneCacheValue.Zone;
56 | }
57 |
58 | //query from uc api
59 | Zone zone;
60 | HttpResult hr = null;
61 | if (String.IsNullOrEmpty(ucHost))
62 | {
63 | ucHost = "https://" + Config.DefaultQueryRegionHost;
64 | }
65 |
66 | if (backupUcHosts == null)
67 | {
68 | backupUcHosts = Config.DefaultBackupQueryRegionHosts;
69 | }
70 |
71 | try
72 | {
73 | string queryUrl = string.Format("{0}/v4/query?ak={1}&bucket={2}",
74 | ucHost,
75 | accessKey,
76 | bucket
77 | );
78 | HttpManager httpManager = new HttpManager();
79 | List middlewares = new List
80 | {
81 | new RetryDomainsMiddleware(
82 | backupUcHosts
83 | )
84 | };
85 | hr = httpManager.Get(queryUrl, null, null, middlewares);
86 | if (hr.Code != (int) HttpCode.OK || string.IsNullOrEmpty(hr.Text))
87 | {
88 | throw new Exception("code: " + hr.Code + ", text: " + hr.Text + ", ref-text:" + hr.RefText);
89 | }
90 |
91 | ZoneInfo zInfo = JsonConvert.DeserializeObject(hr.Text);
92 | if (zInfo == null)
93 | {
94 | throw new Exception("JSON Deserialize failed: " + hr.Text);
95 | }
96 |
97 | if (zInfo.Hosts.Length == 0)
98 | {
99 | throw new Exception("There are no hosts available: " + hr.Text);
100 | }
101 |
102 | ZoneHost zHost = zInfo.Hosts[0];
103 |
104 | zone = new Zone();
105 | zone.SrcUpHosts = zHost.Up.Domains;
106 | zone.CdnUpHosts = zHost.Up.Domains;
107 |
108 | if (!string.IsNullOrEmpty(zHost.Io.Domains[0]))
109 | {
110 | zone.IovipHost = zHost.Io.Domains[0];
111 | }
112 | else
113 | {
114 | zone.IovipHost = Config.DefaultIoHost;
115 | }
116 |
117 | if (!string.IsNullOrEmpty(zHost.Api.Domains[0]))
118 | {
119 | zone.ApiHost = zHost.Api.Domains[0];
120 | }
121 | else
122 | {
123 | zone.ApiHost = Config.DefaultApiHost;
124 | }
125 |
126 | if (!string.IsNullOrEmpty(zHost.Rs.Domains[0]))
127 | {
128 | zone.RsHost = zHost.Rs.Domains[0];
129 | }
130 | else
131 | {
132 | zone.RsHost = Config.DefaultRsHost;
133 | }
134 |
135 | if (!string.IsNullOrEmpty(zHost.Rsf.Domains[0]))
136 | {
137 | zone.RsfHost = zHost.Rsf.Domains[0];
138 | }
139 | else
140 | {
141 | zone.RsfHost = Config.DefaultRsfHost;
142 | }
143 |
144 | lock (rwLock)
145 | {
146 | zoneCacheValue = new ZoneCacheValue();
147 | TimeSpan ttl = TimeSpan.FromSeconds(zHost.Ttl);
148 | zoneCacheValue.Deadline = DateTime.Now.Add(ttl);
149 | zoneCacheValue.Zone = zone;
150 | zoneCache[cacheKey] = zoneCacheValue;
151 | }
152 | }
153 | catch (Exception ex)
154 | {
155 | StringBuilder sb = new StringBuilder();
156 | sb.AppendFormat("[{0}] QueryZone Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"));
157 | Exception e = ex;
158 | while (e != null)
159 | {
160 | sb.Append(e.Message + " ");
161 | e = e.InnerException;
162 | }
163 | sb.AppendLine();
164 |
165 | throw new QiniuException(hr, sb.ToString());
166 | }
167 |
168 | return zone;
169 | }
170 | }
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/src/QiniuTests/Http/HttpRequestOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Specialized;
3 | using System.Net;
4 | using NUnit.Framework;
5 | using Qiniu.Http;
6 |
7 | namespace QiniuTests.Http
8 | {
9 | [TestFixture]
10 | public class HttpRequestOptionsTests
11 | {
12 | [Test]
13 | public void SetUrlTest()
14 | {
15 | HttpRequestOptions reqOpts = new HttpRequestOptions();
16 | reqOpts.Url = "https://qiniu.com/index.html";
17 |
18 | HttpWebRequest wReq = reqOpts.CreateHttpWebRequest();
19 | Assert.AreEqual("https://qiniu.com/index.html", wReq.Address.ToString());
20 | wReq.Abort();
21 |
22 | reqOpts.Url = "https://www.qiniu.com/index.html";
23 | wReq = reqOpts.CreateHttpWebRequest();
24 | Assert.AreEqual("https://www.qiniu.com/index.html", wReq.Address.ToString());
25 | wReq.Abort();
26 | }
27 |
28 | [Test]
29 | public void SetPropertiesTest()
30 | {
31 | HttpRequestOptions reqOpts = new HttpRequestOptions();
32 | reqOpts.Url = "https://qiniu.com/index.html";
33 |
34 | // some items are hard to set and test, skipped.
35 | reqOpts.AllowAutoRedirect = false; // default true
36 | reqOpts.AllowReadStreamBuffering = true; // default false
37 | reqOpts.AllowWriteStreamBuffering = false; // default true
38 | // reqOpts.AutomaticDecompression = ;
39 | // reqOpts.CachePolicy =;
40 | // reqOpts.ClientCertificates = System.Security.Cryptography.X509Certificates.X509CertificateCollection;
41 | reqOpts.ConnectionGroupName = "qngroup"; // default ""
42 | // reqOpts.ContinueDelegate =;
43 | reqOpts.ContinueTimeout = 360; // default 350
44 | // reqOpts.CookieContainer =;
45 | // reqOpts.Credentials =;
46 | // reqOpts.ImpersonationLevel = Delegation;
47 | reqOpts.KeepAlive = false; // default true
48 | reqOpts.MaximumAutomaticRedirections = 10; // default 50
49 | reqOpts.MaximumResponseHeadersLength = 32; // default 64
50 | reqOpts.MediaType = "video/mp4"; // default ""
51 | reqOpts.Method = "POST"; // default "GET"
52 | reqOpts.Pipelined = false; // default true
53 | reqOpts.PreAuthenticate = true; // default false
54 | // reqOpts.Proxy = System.Net.SystemWebProxy;
55 | reqOpts.ReadWriteTimeout = 200000; // default 300000
56 | reqOpts.SendChunked = true; // default false
57 | // reqOpts.ServerCertificateValidationCallback =;
58 | reqOpts.Timeout = 50000; // default 100000
59 | reqOpts.UnsafeAuthenticatedConnectionSharing = true; // default false
60 | reqOpts.UseDefaultCredentials = true; // default false
61 |
62 | HttpWebRequest wReq = reqOpts.CreateHttpWebRequest();
63 | Assert.AreEqual(false, wReq.AllowAutoRedirect); // default true
64 | Assert.AreEqual(true, wReq.AllowReadStreamBuffering); // default false
65 | Assert.AreEqual(false, wReq.AllowWriteStreamBuffering); // default true
66 | // Assert.AreEqual(, wReq.AutomaticDecompression); // default Null
67 | // Assert(, wReq.CachePolicy); // default Null
68 | // Assert(, wReq.ClientCertificates); // default System.Security.Cryptography.X509Certificates.X509CertificateCollection
69 | Assert.AreEqual("qngroup", wReq.ConnectionGroupName); // default ""
70 | // Assert(, wReq.ContinueDelegate); // default Null
71 | Assert.AreEqual(360, wReq.ContinueTimeout); // default 350
72 | // Assert(, wReq.CookieContainer); // default Null
73 | // Assert(, wReq.Credentials); // default Null
74 | // Assert(, wReq.ImpersonationLevel); // default Null
75 | Assert.AreEqual(false, wReq.KeepAlive); // default true
76 | Assert.AreEqual(10, wReq.MaximumAutomaticRedirections); // default 50
77 | Assert.AreEqual(32, wReq.MaximumResponseHeadersLength); // default 64
78 | Assert.AreEqual("video/mp4", wReq.MediaType); // default ""
79 | Assert.AreEqual("POST", wReq.Method); // default "GET"
80 | Assert.AreEqual(false, wReq.Pipelined); // default true
81 | Assert.AreEqual(true, wReq.PreAuthenticate); // default false
82 | // Assert(, wReq.Proxy); // default System.Net.SystemWebProxy;
83 | Assert.AreEqual(200000, wReq.ReadWriteTimeout); // default 300000
84 | Assert.AreEqual(true, wReq.SendChunked); // default false
85 | // Assert(, wReq.ServerCertificateValidationCallback); // default Null
86 | Assert.AreEqual(50000, wReq.Timeout); // default 100000
87 | Assert.AreEqual(true, wReq.UnsafeAuthenticatedConnectionSharing); // default false
88 | Assert.AreEqual(true, wReq.UseDefaultCredentials); // default false
89 |
90 | wReq.Abort();
91 | }
92 |
93 | [Test]
94 | public void SetHeadersTest()
95 | {
96 | HttpRequestOptions reqOpts = new HttpRequestOptions();
97 | reqOpts.Url = "https://qiniu.com/index.html";
98 |
99 | reqOpts.Headers = new StringDictionary
100 | {
101 | { "Accept", "text/plain" },
102 | { "content-Type", "text/plain" },
103 | { "date", "Wed, 03 Aug 2011 04:00:00 GMT" },
104 | { "expect", "200-ok" },
105 | { "host", "qiniu.com" },
106 | { "if-modified-since", "Wed, 03 Aug 2011 04:00:00 GMT" },
107 | { "referer", "https://qiniu.com/" },
108 | { "transfer-encoding", "gzip" },
109 | { "user-agent", "qn-csharp-sdk" },
110 | { "X-Qiniu-A", "qn" }
111 | };
112 |
113 | // TransferEncoding requires the SendChunked property to be set to true
114 | reqOpts.SendChunked = true;
115 |
116 | HttpWebRequest wReq = reqOpts.CreateHttpWebRequest();
117 | Assert.AreEqual("text/plain", wReq.Accept);
118 | Assert.AreEqual("text/plain", wReq.ContentType);
119 | Assert.AreEqual(DateTime.Parse("Wed, 03 Aug 2011 04:00:00 GMT"), wReq.Date);
120 | Assert.AreEqual("200-ok", wReq.Expect);
121 | Assert.AreEqual("qiniu.com", wReq.Host);
122 | Assert.AreEqual(DateTime.Parse("Wed, 03 Aug 2011 04:00:00 GMT"), wReq.IfModifiedSince);
123 | Assert.AreEqual("https://qiniu.com/", wReq.Referer);
124 | Assert.AreEqual("gzip", wReq.TransferEncoding);
125 | Assert.AreEqual("qn-csharp-sdk", wReq.UserAgent);
126 | Assert.AreEqual("qn", wReq.Headers["X-Qiniu-A"]);
127 |
128 | wReq.Abort();
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/src/Qiniu/Storage/PutPolicy.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace Qiniu.Storage
4 | {
5 | ///
6 | /// 上传策略
7 | /// 参考文档:https://developer.qiniu.com/kodo/manual/1206/put-policy
8 | ///
9 | public class PutPolicy
10 | {
11 | ///
12 | /// [必需]bucket或者bucket:key
13 | ///
14 | [JsonProperty("scope")]
15 | public string Scope { get; set; }
16 |
17 | ///
18 | /// [可选]若为 1,表示允许用户上传以 scope 的 keyPrefix 为前缀的文件。
19 | ///
20 | [JsonProperty("isPrefixalScope", NullValueHandling = NullValueHandling.Ignore)]
21 | public int? isPrefixalScope { get; set; }
22 |
23 | ///
24 | /// [必需]上传策略失效时刻,请使用SetExpire来设置它
25 | ///
26 | [JsonProperty("deadline")]
27 | public long Deadline { get; private set; }
28 |
29 | ///
30 | /// [可选]"仅新增"模式
31 | ///
32 | [JsonProperty("insertOnly", NullValueHandling = NullValueHandling.Ignore)]
33 | public int? InsertOnly { get; set; }
34 |
35 | ///
36 | /// [可选]saveKey 的优先级设置。为 true 时,saveKey不能为空,会忽略客户端指定的key,强制使用saveKey进行文件命名。
37 | /// 默认为 false
38 | ///
39 | [JsonProperty("forceSaveKey", NullValueHandling = NullValueHandling.Ignore)]
40 | public bool? ForceSaveKey { get; set; }
41 |
42 | ///
43 | /// [可选]保存文件的key
44 | ///
45 | [JsonProperty("saveKey", NullValueHandling = NullValueHandling.Ignore)]
46 | public string SaveKey { get; set; }
47 |
48 | ///
49 | /// [可选]终端用户
50 | ///
51 | [JsonProperty("endUser", NullValueHandling = NullValueHandling.Ignore)]
52 | public string EndUser { get; set; }
53 |
54 | ///
55 | /// [可选]返回URL
56 | ///
57 | [JsonProperty("returnUrl", NullValueHandling = NullValueHandling.Ignore)]
58 | public string ReturnUrl { get; set; }
59 |
60 | ///
61 | /// [可选]返回内容
62 | ///
63 | [JsonProperty("returnBody", NullValueHandling = NullValueHandling.Ignore)]
64 | public string ReturnBody { get; set; }
65 |
66 | ///
67 | /// [可选]回调URL
68 | ///
69 | [JsonProperty("callbackUrl", NullValueHandling = NullValueHandling.Ignore)]
70 | public string CallbackUrl { get; set; }
71 |
72 | ///
73 | /// [可选]回调内容
74 | ///
75 | [JsonProperty("callbackBody", NullValueHandling = NullValueHandling.Ignore)]
76 | public string CallbackBody { get; set; }
77 |
78 | ///
79 | /// [可选]回调内容类型
80 | ///
81 | [JsonProperty("callbackBodyType", NullValueHandling = NullValueHandling.Ignore)]
82 | public string CallbackBodyType { get; set; }
83 |
84 | ///
85 | /// [可选]回调host
86 | ///
87 | [JsonProperty("callbackHost", NullValueHandling = NullValueHandling.Ignore)]
88 | public string CallbackHost { get; set; }
89 |
90 | ///
91 | /// [可选]回调fetchkey
92 | ///
93 | [JsonProperty("callbackFetchKey", NullValueHandling = NullValueHandling.Ignore)]
94 | public int? CallbackFetchKey { get; set; }
95 |
96 | ///
97 | /// [可选]上传预转持久化,与 PersistentWorkflowTemplateId 二选一
98 | ///
99 | [JsonProperty("persistentOps", NullValueHandling = NullValueHandling.Ignore)]
100 | public string PersistentOps { get; set; }
101 |
102 | ///
103 | /// [可选]持久化结果通知
104 | ///
105 | [JsonProperty("persistentNotifyUrl", NullValueHandling = NullValueHandling.Ignore)]
106 | public string PersistentNotifyUrl { get; set; }
107 |
108 | ///
109 | /// [可选]私有队列
110 | ///
111 | [JsonProperty("persistentPipeline", NullValueHandling = NullValueHandling.Ignore)]
112 | public string PersistentPipeline { get; set; }
113 |
114 | ///
115 | /// [可选]持久化任务类型,为 1 时开启闲时任务
116 | ///
117 | [JsonProperty("persistentType", NullValueHandling = NullValueHandling.Ignore)]
118 | public int? PersistentType { get; set; }
119 |
120 | ///
121 | /// [可选]任务模版,与 PersistentOps 二选一
122 | ///
123 | [JsonProperty("persistentWorkflowTemplateID", NullValueHandling = NullValueHandling.Ignore)]
124 | public string PersistentWorkflowTemplateId { get; set; }
125 |
126 |
127 | ///
128 | /// [可选]上传文件大小限制:最小值,单位Byte
129 | ///
130 | [JsonProperty("fsizeMin", NullValueHandling = NullValueHandling.Ignore)]
131 | public long? FsizeMin { get; set; }
132 |
133 | ///
134 | /// [可选]上传文件大小限制:最大值,单位Byte
135 | ///
136 | [JsonProperty("fsizeLimit", NullValueHandling = NullValueHandling.Ignore)]
137 | public long? FsizeLimit { get; set; }
138 |
139 | ///
140 | /// [可选]上传时是否自动检测MIME
141 | ///
142 | [JsonProperty("detectMime", NullValueHandling = NullValueHandling.Ignore)]
143 | public int? DetectMime { get; set; }
144 |
145 | ///
146 | /// [可选]上传文件MIME限制
147 | ///
148 | [JsonProperty("mimeLimit", NullValueHandling = NullValueHandling.Ignore)]
149 | public string MimeLimit { get; set; }
150 |
151 | ///
152 | /// [可选]文件上传后多少天后自动删除
153 | ///
154 | [JsonProperty("deleteAfterDays", NullValueHandling = NullValueHandling.Ignore)]
155 | public int? DeleteAfterDays { get; set; }
156 |
157 | ///
158 | /// [可选]文件的存储类型,默认为普通存储,设置为:0 标准存储(默认),1 低频存储,2 归档存储,3 深度归档存储
159 | ///
160 | [JsonProperty("fileType", NullValueHandling = NullValueHandling.Ignore)]
161 | public int? FileType { get; set; }
162 |
163 | ///
164 | /// 设置上传凭证有效期(配置Deadline属性)
165 | ///
166 | ///
167 | public void SetExpires(int expireInSeconds)
168 | {
169 | this.Deadline = Util.UnixTimestamp.GetUnixTimestamp(expireInSeconds);
170 | }
171 |
172 | public void SetExpires(long expireInSeconds)
173 | {
174 | this.Deadline = Util.UnixTimestamp.GetUnixTimestamp(expireInSeconds);
175 | }
176 |
177 | ///
178 | /// 转换为JSON字符串
179 | ///
180 | /// JSON字符串
181 | public string ToJsonString()
182 | {
183 | if (this.Deadline == 0)
184 | {
185 | //默认一个小时有效期
186 | this.SetExpires(3600);
187 | }
188 | return JsonConvert.SerializeObject(this);
189 | }
190 |
191 | }
192 | }
193 |
--------------------------------------------------------------------------------