├── .gitignore ├── demo ├── jpegToBase64.cs ├── Properties │ └── AssemblyInfo.cs ├── demo.csproj ├── Program.cs └── Base64File.cs ├── sdk ├── Properties │ └── AssemblyInfo.cs ├── QiniuUtils.cs ├── QiniuException.cs ├── QiniuConf.cs ├── QiniuErrors.cs ├── qiniu.csproj ├── QiniuMAC.cs ├── QiniuResumbleUploadEx.cs ├── QiniuEventArgs.cs ├── QiniuPutPolicy.cs ├── QiniuWebClient.cs └── QiniuFile.cs ├── LICENSE ├── qiniu-csharp-sdk.sln └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Libraries 8 | *.lib 9 | *.a 10 | 11 | # Shared objects (inc. Windows DLLs) 12 | *.dll 13 | *.so 14 | *.so.* 15 | *.dylib 16 | 17 | # Executables 18 | *.exe 19 | *.out 20 | *.app 21 | *.i*86 22 | *.x86_64 23 | *.hex 24 | 25 | obj 26 | bin 27 | qiniu-csharp-sdk.userprefs 28 | -------------------------------------------------------------------------------- /demo/jpegToBase64.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace demo 5 | { 6 | public class jpegToBase64 7 | { 8 | string filename; 9 | long filesize; 10 | string base64Content; 11 | 12 | public string Filename { 13 | get { return filename; } 14 | } 15 | 16 | public long Filesize { 17 | get { return filesize; } 18 | } 19 | 20 | public string Base64Content { 21 | get { return base64Content; } 22 | } 23 | 24 | public jpegToBase64 (string filename) 25 | { 26 | if (!File.Exists (filename)) { 27 | throw new ArgumentException (filename + " not exists"); 28 | } 29 | this.filename = filename; 30 | FileInfo finfo = new FileInfo (filename); 31 | this.filesize = finfo.Length; 32 | using (FileStream fs = finfo.OpenRead ()) { 33 | byte[] content = new byte[this.filesize]; 34 | fs.Read (content, 0, (int)this.filesize); 35 | this.base64Content = Convert.ToBase64String (content); 36 | } 37 | } 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /demo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | [assembly: AssemblyTitle ("demo")] 7 | [assembly: AssemblyDescription ("")] 8 | [assembly: AssemblyConfiguration ("")] 9 | [assembly: AssemblyCompany ("")] 10 | [assembly: AssemblyProduct ("")] 11 | [assembly: AssemblyCopyright ("icattlecoder")] 12 | [assembly: AssemblyTrademark ("")] 13 | [assembly: AssemblyCulture ("")] 14 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 15 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 16 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 17 | [assembly: AssemblyVersion ("1.0.*")] 18 | // The following attributes are used to specify the signing key for the assembly, 19 | // if desired. See the Mono documentation for more information about signing. 20 | //[assembly: AssemblyDelaySign(false)] 21 | //[assembly: AssemblyKeyFile("")] 22 | 23 | -------------------------------------------------------------------------------- /sdk/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | [assembly: AssemblyTitle ("qiniu-csharp-sdk")] 7 | [assembly: AssemblyDescription ("")] 8 | [assembly: AssemblyConfiguration ("")] 9 | [assembly: AssemblyCompany ("")] 10 | [assembly: AssemblyProduct ("")] 11 | [assembly: AssemblyCopyright ("icattlecoder")] 12 | [assembly: AssemblyTrademark ("")] 13 | [assembly: AssemblyCulture ("")] 14 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 15 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 16 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 17 | [assembly: AssemblyVersion ("1.0.*")] 18 | // The following attributes are used to specify the signing key for the assembly, 19 | // if desired. See the Mono documentation for more information about signing. 20 | //[assembly: AssemblyDelaySign(false)] 21 | //[assembly: AssemblyKeyFile("")] 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 wangming 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. -------------------------------------------------------------------------------- /sdk/QiniuUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Encoding = System.Text.UTF8Encoding; 3 | 4 | namespace qiniu 5 | { 6 | public static class Base64URLSafe 7 | { 8 | public static string Encode (string text) 9 | { 10 | if (String.IsNullOrEmpty (text)) 11 | return ""; 12 | byte[] bs = Encoding.UTF8.GetBytes (text); 13 | string encodedStr = Convert.ToBase64String (bs); 14 | encodedStr = encodedStr.Replace ('+', '-').Replace ('/', '_'); 15 | return encodedStr; 16 | } 17 | 18 | /// 19 | /// To the base64 safe URL. 20 | /// 21 | /// The base64 URL safe. 22 | /// String. 23 | public static string ToBase64URLSafe (string str) 24 | { 25 | return Encode (str); 26 | } 27 | 28 | /// 29 | /// Encode the specified bs. 30 | /// 31 | /// Bs. 32 | public static string Encode (byte[] bs) 33 | { 34 | if (bs == null || bs.Length == 0) 35 | return ""; 36 | string encodedStr = Convert.ToBase64String (bs); 37 | encodedStr = encodedStr.Replace ('+', '-').Replace ('/', '_'); 38 | return encodedStr; 39 | } 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /sdk/QiniuException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace qiniu 5 | { 6 | /// 7 | /// Qiniu web exception. 8 | /// 9 | public class QiniuWebException:Exception 10 | { 11 | 12 | #region 13 | private QiniuErrors error; 14 | private string x_Reqid; 15 | private string x_Log; 16 | #endregion 17 | 18 | /// 19 | /// Gets the x_reqid. 20 | /// 21 | /// The x_reqid. 22 | public string X_Reqid { 23 | get { return x_Reqid; } 24 | } 25 | 26 | /// 27 | /// Gets the x_log. 28 | /// 29 | /// The x_log. 30 | public string X_Log { 31 | get { return x_Log; } 32 | } 33 | 34 | /// 35 | /// Gets the error. 36 | /// 37 | /// The error. 38 | public QiniuErrors Error { 39 | get { return error; } 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the class. 44 | /// 45 | /// We. 46 | public QiniuWebException (WebException we) { 47 | if (we.Response is HttpWebResponse) { 48 | HttpWebResponse hwr = we.Response as HttpWebResponse; 49 | this.error = new QiniuErrors ((int)hwr.StatusCode); 50 | this.x_Reqid = hwr.Headers ["X-Reqid"]; 51 | this.x_Log = hwr.Headers ["X-Log"]; 52 | } 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /sdk/QiniuConf.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace qiniu 5 | { 6 | /// 7 | /// Config. 8 | /// 9 | public class Config 10 | { 11 | public static string USER_AGENT = "qiniu-csharp-sdk icattlecoder v1.0.0"; 12 | #region 帐户信息 13 | /// 14 | /// 七牛提供的公钥,用于识别用户 15 | /// 16 | public static string ACCESS_KEY = ""; 17 | /// 18 | /// 七牛提供的秘钥,不要在客户端初始化该变量 19 | /// 20 | public static string SECRET_KEY = ""; 21 | #endregion 22 | 23 | #region 七牛服务器地址 24 | /// 25 | /// 七牛资源管理服务器地址 26 | /// 27 | public static string RS_HOST = "http://rs.Qbox.me"; 28 | /// 29 | /// 七牛资源上传服务器地址. 30 | /// 31 | public static string UP_HOST = "http://up.qiniu.com"; 32 | /// 33 | /// 七牛资源列表服务器地址. 34 | /// 35 | public static string RSF_HOST = "http://rsf.Qbox.me"; 36 | 37 | public static string PREFETCH_HOST = "http://iovip.qbox.me"; 38 | 39 | public static string API_HOST = "http://api.qiniu.com"; 40 | #endregion 41 | 42 | /// 43 | /// 初始化七牛帐户、请求地址等信息,不应在客户端调用。 44 | /// 45 | public static void InitFromAppConfig() 46 | { 47 | ACCESS_KEY = System.Configuration.ConfigurationManager.AppSettings["ACCESS_KEY"]; 48 | SECRET_KEY = System.Configuration.ConfigurationManager.AppSettings["SECRET_KEY"]; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /qiniu-csharp-sdk.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qiniu", "sdk\qiniu.csproj", "{8D120ACA-4BF9-42A7-81C6-8BE39FF5ED42}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "demo", "demo\demo.csproj", "{4F38040D-651B-4273-AE85-D96BC59C4698}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {4F38040D-651B-4273-AE85-D96BC59C4698}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {4F38040D-651B-4273-AE85-D96BC59C4698}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {4F38040D-651B-4273-AE85-D96BC59C4698}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {4F38040D-651B-4273-AE85-D96BC59C4698}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {8D120ACA-4BF9-42A7-81C6-8BE39FF5ED42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {8D120ACA-4BF9-42A7-81C6-8BE39FF5ED42}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {8D120ACA-4BF9-42A7-81C6-8BE39FF5ED42}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {8D120ACA-4BF9-42A7-81C6-8BE39FF5ED42}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(MonoDevelopProperties) = preSolution 24 | StartupItem = demo\demo.csproj 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /sdk/QiniuErrors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace qiniu 5 | { 6 | public class QiniuErrors 7 | { 8 | /// 9 | /// See http://developer.qiniu.com/docs/v6/api/reference/codes.html 10 | /// 11 | public static Dictionary ErrorCodes = new Dictionary { 12 | {298,"部分操作执行成功"}, 13 | {400,"请求报文格式错误"}, 14 | {401,"认证授权失败,可能是密钥信息不正确、数字签名错误或授权已超时"}, 15 | {404,"资源不存在"}, 16 | {405,"请求方式错误,非预期的请求方式"}, 17 | {406,"上传的数据 CRC32 校验错"}, 18 | {419,"用户账号被冻结"}, 19 | {503,"服务端不可用"}, 20 | {504,"服务端操作超时"}, 21 | {579,"资源上传成功,但是回调失败"}, 22 | {599,"服务端操作失败"}, 23 | {608,"资源内容被修改"}, 24 | {612,"指定资源不存在或已被删除"}, 25 | {614,"目标资源已存在"}, 26 | {630,"已创建的空间数量达到上限,无法创建新空间"}, 27 | {631,"指定空间不存在"}, 28 | {701,"上传数据块校验出错"} 29 | }; 30 | 31 | private int httpCode; 32 | 33 | /// 34 | /// Gets the http code. 35 | /// 36 | /// The http code. 37 | public int HttpCode { 38 | get { return httpCode; } 39 | } 40 | 41 | /// 42 | /// Initializes a new instance of the class. 43 | /// 44 | /// Code. 45 | public QiniuErrors (int code) 46 | { 47 | this.httpCode = code; 48 | } 49 | 50 | /// 51 | /// Returns a that represents the current . 52 | /// 53 | /// A that represents the current . 54 | public override string ToString () 55 | { 56 | return ErrorCodes [this.httpCode]; 57 | } 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /demo/demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 10.0.0 7 | 2.0 8 | {4F38040D-651B-4273-AE85-D96BC59C4698} 9 | Exe 10 | demo 11 | demo 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\Debug 18 | DEBUG; 19 | prompt 20 | 4 21 | true 22 | 23 | 24 | full 25 | true 26 | bin\Release 27 | prompt 28 | 4 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {8D120ACA-4BF9-42A7-81C6-8BE39FF5ED42} 44 | qiniu 45 | 46 | 47 | -------------------------------------------------------------------------------- /sdk/qiniu.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 10.0.0 7 | 2.0 8 | {8D120ACA-4BF9-42A7-81C6-8BE39FF5ED42} 9 | Library 10 | qiniucsharpsdk 11 | qiniu-csharp-sdk 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\Debug 18 | DEBUG; 19 | prompt 20 | 4 21 | false 22 | 23 | 24 | full 25 | true 26 | bin\Release 27 | prompt 28 | 4 29 | false 30 | 31 | 32 | 33 | 34 | 35 | 36 | bin\Debug\Newtonsoft.Json.dll 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /sdk/QiniuMAC.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | using Encoding = System.Text.UTF8Encoding; 5 | 6 | namespace qiniu 7 | { 8 | public class MAC 9 | { 10 | 11 | private string accessKey; 12 | private byte[] secretKey; 13 | 14 | /// 15 | /// Gets or sets the access key. 16 | /// 17 | /// The access key. 18 | public string AccessKey { 19 | get { return accessKey; } 20 | set { accessKey = value; } 21 | } 22 | 23 | /// 24 | /// Gets or sets the secret key. 25 | /// 26 | /// The secret key. 27 | public string SecretKey { 28 | set { secretKey = Encoding.UTF8.GetBytes(value); } 29 | } 30 | 31 | /// 32 | /// Initializes a new instance of the class. 33 | /// 34 | /// Access key. 35 | /// Secret key. 36 | public MAC (string accessKey,string secretKey ) 37 | { 38 | this.accessKey = accessKey; 39 | SecretKey = secretKey; 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the class. 44 | /// Get accesskey and secretKey from the Config 45 | /// 46 | public MAC() 47 | { 48 | this.accessKey = Config.ACCESS_KEY; 49 | SecretKey = Config.SECRET_KEY; 50 | } 51 | 52 | private string _sign (byte[] data) 53 | { 54 | HMACSHA1 hmac = new HMACSHA1 (this.secretKey); 55 | byte[] digest = hmac.ComputeHash (data); 56 | return Base64URLSafe.Encode (digest); 57 | } 58 | 59 | /// 60 | /// Sign 61 | /// 62 | /// 63 | /// 64 | public string Sign (byte[] b) 65 | { 66 | return string.Format ("{0}:{1}", this.accessKey, _sign (b)); 67 | } 68 | 69 | 70 | /// 71 | /// SignWithData 72 | /// 73 | /// 74 | /// 75 | public string SignWithData (byte[] b) 76 | { 77 | string data = Base64URLSafe.Encode (b); 78 | return string.Format ("{0}:{1}:{2}", this.accessKey, _sign (Encoding.UTF8.GetBytes (data)), data); 79 | } 80 | 81 | /// 82 | /// SignRequest 83 | /// 84 | /// 85 | /// 86 | /// 87 | public void SignRequest (System.Net.HttpWebRequest request, byte[] body) 88 | { 89 | Uri u = request.Address; 90 | using (HMACSHA1 hmac = new HMACSHA1(secretKey)) { 91 | string pathAndQuery = request.Address.PathAndQuery; 92 | byte[] pathAndQueryBytes = Encoding.UTF8.GetBytes (pathAndQuery); 93 | using (MemoryStream buffer = new MemoryStream()) { 94 | buffer.Write (pathAndQueryBytes, 0, pathAndQueryBytes.Length); 95 | buffer.WriteByte ((byte)'\n'); 96 | if (body!=null&&body.Length > 0) { 97 | buffer.Write (body, 0, body.Length); 98 | } 99 | byte[] digest = hmac.ComputeHash (buffer.ToArray ()); 100 | string digestBase64 = Base64URLSafe.Encode (digest); 101 | request.Headers.Add ("Authorization", "QBox " + this.accessKey+":"+digestBase64); 102 | } 103 | } 104 | } 105 | 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /demo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using qiniu; 5 | using System.Threading; 6 | 7 | namespace demo 8 | { 9 | class MainClass 10 | { 11 | public static void Main (string[] args) 12 | { 13 | // 初始化qiniu配置,主要是API Keys 14 | qiniu.Config.ACCESS_KEY = "IT9iP3J9wdXXYsT1p8ns0gWD-CQOdLvIQuyE0FOk"; 15 | qiniu.Config.SECRET_KEY = "zUCzekBtEqTZ4-WJPCGlBrr2PeyYxsYn98LPaivM"; 16 | 17 | /********************************************************************** 18 | 可以用下面的方法从配置文件中初始化 19 | qiniu.Config.InitFromAppConfig (); 20 | **********************************************************************/ 21 | 22 | string localfile = "/Users/icattlecoder/Movies/tzd.rmvb"; 23 | string bucket = "icattlecoder"; 24 | string qiniukey = "tzd.rmvb"; 25 | 26 | { 27 | Base64File bfile = new Base64File (Base64File.TESTBASE64); 28 | bfile.Save ("/Users/icattlecoder/Desktop/dbase64.jpg"); 29 | } 30 | 31 | { 32 | UploadBase642 (); 33 | } 34 | 35 | { 36 | UploadBase64 (); 37 | } 38 | 39 | //====================================================================== 40 | { 41 | QiniuFile qfile = new QiniuFile (bucket, qiniukey, localfile); 42 | // ResumbleUploadEx puttedCtx = new ResumbleUploadEx (localfile); 43 | ManualResetEvent done = new ManualResetEvent (false); 44 | qfile.UploadCompleted += (sender, e) => { 45 | Console.WriteLine (e.key); 46 | Console.WriteLine (e.Hash); 47 | done.Set (); 48 | }; 49 | qfile.UploadFailed += (sender, e) => { 50 | Console.WriteLine (e.Error.ToString ()); 51 | // puttedCtx.Save(); 52 | done.Set (); 53 | }; 54 | 55 | qfile.UploadProgressChanged += (sender, e) => { 56 | int percentage = (int)(100 * e.BytesSent / e.TotalBytes); 57 | Console.Write (percentage); 58 | }; 59 | qfile.UploadBlockCompleted += (sender, e) => { 60 | // puttedCtx.Add(e.Index,e.Ctx); 61 | // puttedCtx.Save(); 62 | }; 63 | qfile.UploadBlockFailed += (sender, e) => { 64 | // 65 | 66 | }; 67 | 68 | //上传为异步操作 69 | //上传本地文件到七牛云存储 70 | // qfile.Upload (puttedCtx.PuttedCtx); 71 | qfile.Upload (); 72 | done.WaitOne (); 73 | } 74 | 75 | //====================================================================== 76 | { 77 | /* 78 | 79 | try { 80 | QiniuFile qfile = new QiniuFile (bucket, qiniukey); 81 | QiniuFileInfo finfo = qfile.Stat (); 82 | if (finfo != null) { 83 | qfile.Move("cloudcomment","movetest"); 84 | //删除七牛云空间的文件 85 | //qfile.Delete (); 86 | } 87 | } catch (QiniuWebException e) { 88 | Console.WriteLine (e.Error.HttpCode); 89 | Console.WriteLine (e.Error.ToString ()); 90 | } 91 | */ 92 | } 93 | } 94 | 95 | /// 96 | /// Uploads the base64. 97 | /// 98 | public static void UploadBase64(){ 99 | string bucket = "icattlecoder"; 100 | string qiniuKey = "base64.png"; 101 | 102 | ManualResetEvent done = new ManualResetEvent (false); 103 | jpegToBase64 jpeg = new jpegToBase64 ("/Users/icattlecoder/Desktop/base64.png"); 104 | QiniuFile qfile = new QiniuFile (bucket, qiniuKey); 105 | qfile.UploadCompleted+= (sender, e) => { 106 | Console.Write(e.RawString); 107 | done.Set(); 108 | 109 | }; 110 | qfile.UploadFailed+= (sender, e) => { 111 | QiniuWebException qe = (QiniuWebException)e.Error; 112 | Console.WriteLine(qe.Error.ToString()); 113 | }; 114 | qfile.UploadString ((int)jpeg.Filesize, "image/png", jpeg.Base64Content); 115 | done.WaitOne (); 116 | } 117 | 118 | public static void UploadBase642(){ 119 | string bucket = "icattlecoder"; 120 | string qiniuKey = "base642.png"; 121 | 122 | ManualResetEvent done = new ManualResetEvent (false); 123 | Base64File bfile = new Base64File (Base64File.TESTBASE64); 124 | 125 | 126 | QiniuFile qfile = new QiniuFile (bucket, qiniuKey); 127 | qfile.UploadCompleted+= (sender, e) => { 128 | Console.Write(e.RawString); 129 | done.Set(); 130 | 131 | }; 132 | qfile.UploadFailed+= (sender, e) => { 133 | QiniuWebException qe = (QiniuWebException)e.Error; 134 | Console.WriteLine(qe.Error.ToString()); 135 | }; 136 | qfile.UploadString ((int)bfile.FileSize, "image/png", Base64File.TESTBASE64); 137 | done.WaitOne (); 138 | 139 | } 140 | 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /sdk/QiniuResumbleUploadEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Security.Cryptography; 5 | using System.Collections.Generic; 6 | 7 | namespace qiniu 8 | { 9 | /// 10 | /// 断点续传. 11 | /// 12 | public class ResumbleUploadEx 13 | { 14 | private string puttedCtxDir; 15 | private string fileName; 16 | private string puttedCtxFileName; 17 | private Dictionary puttedCtx; 18 | 19 | /// 20 | /// 获取已上传的结果. 21 | /// 22 | /// The putted context. 23 | public BlkputRet[] PuttedCtx { 24 | get { 25 | if (this.puttedCtx == null || this.puttedCtx.Count == 0) 26 | return null; 27 | BlkputRet[] result = new BlkputRet[this.puttedCtx.Count]; 28 | for (int i = 0; i < this.puttedCtx.Count; i++) { 29 | if (this.puttedCtx.ContainsKey (i)) { 30 | result [i] = this.puttedCtx [i]; 31 | } else { 32 | this.Clear (); 33 | return null; 34 | } 35 | } 36 | return result; 37 | } 38 | } 39 | 40 | /// 41 | /// Initializes a new instance of the class. 42 | /// 43 | /// Filename. 44 | /// 断点续传上传结果持久化存放目录. 默认存放在系统临时文件夹。 45 | public ResumbleUploadEx (string filename, string puttedCtxDir=null){ 46 | if (!File.Exists (filename)) { 47 | throw new Exception(string.Format("{0} does not exist", filename)); 48 | } 49 | this.fileName = filename; 50 | if (puttedCtxDir != null) { 51 | if (!Directory.Exists (puttedCtxDir)) { 52 | try{ 53 | Directory.CreateDirectory (puttedCtxDir); 54 | } 55 | catch(Exception e){ 56 | throw e; 57 | } 58 | } 59 | this.puttedCtxDir = puttedCtxDir; 60 | } else { 61 | this.puttedCtxDir = Path.GetTempPath (); 62 | } 63 | string ctxfile = getFileBase64Sha1 (this.fileName); 64 | this.puttedCtxFileName = Path.Combine (this.puttedCtxDir, ctxfile); 65 | if (!File.Exists (this.puttedCtxFileName)) { 66 | File.Open (this.puttedCtxFileName, FileMode.Create); 67 | this.puttedCtx = new Dictionary (); 68 | } else { 69 | this.puttedCtx = initloadPuttedCtx (); 70 | } 71 | } 72 | 73 | private Dictionary initloadPuttedCtx(){ 74 | 75 | Dictionary result = new Dictionary (); 76 | string[] lines = File.ReadAllLines(this.puttedCtxFileName); 77 | foreach (string line in lines) 78 | { 79 | string[] fields = line.Split(','); 80 | BlkputRet ret = new BlkputRet(); 81 | ret.offset = ulong.Parse(fields[1]); 82 | ret.ctx = fields[2]; 83 | int idx = int.Parse(fields[0]); 84 | result.Add (idx, ret); 85 | } 86 | return result; 87 | } 88 | 89 | /// 90 | /// 保存持久化结果至磁盘文件 91 | /// 92 | public void Save(){ 93 | StringBuilder sb = new StringBuilder (); 94 | foreach (int i in this.puttedCtx.Keys) { 95 | string content = i + "," + this.puttedCtx[i].offset + "," + this.puttedCtx[i].ctx + "\n"; 96 | sb.Append (content); 97 | } 98 | File.WriteAllText (this.puttedCtxFileName, sb.ToString ()); 99 | } 100 | 101 | /// 102 | /// 保存持久化结果至内存 103 | /// 104 | /// 105 | /// 106 | /// 107 | public void Add(int idx, BlkputRet ret) 108 | { 109 | this.puttedCtx [idx] = ret; 110 | } 111 | 112 | /// 113 | /// 保存持久化结果至内存和文件. 114 | /// 115 | /// Index. 116 | /// Ret. 117 | public void AddAndSave(int idx,BlkputRet ret){ 118 | this.Add (idx, ret); 119 | this.Save (); 120 | } 121 | 122 | /// 123 | /// 清除上传持久化结果 124 | /// 125 | public void Clear(){ 126 | if (File.Exists (this.puttedCtxFileName)) { 127 | this.puttedCtx.Clear (); 128 | File.Delete (this.puttedCtxFileName); 129 | } 130 | } 131 | 132 | /// 133 | /// 获取文件的SHA1值 134 | /// 135 | /// 136 | /// base64编码的sha1值 137 | private static string getFileBase64Sha1(string filename) 138 | { 139 | SHA1 sha1 = new SHA1CryptoServiceProvider(); 140 | using (Stream reader = System.IO.File.OpenRead(filename)) 141 | { 142 | byte[] result = sha1.ComputeHash(reader); 143 | return BitConverter.ToString(result); 144 | } 145 | } 146 | } 147 | } 148 | 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | qiniu-csharp-sdk 2 | ================ 3 | 4 | 七牛云存储SDK 5 | 6 | 此SDK实现了七牛云存储的核心部分,即文件上传,目的是简化文件上传并提供更加便捷的编程接口,从更高层面进行了抽象,而非官方SDK那样,仅是对API的一套封装。 7 | 8 | 项目地址: [https://github.com/icattlecoder/qiniu-csharp-sdk](https://github.com/icattlecoder/qiniu-csharp-sdk) 9 | 10 | - [初始化](#init) 11 | - [上传文件](#upload) 12 | - [上传事件](#event) 13 | - [上传结果](#result) 14 | - [续传](#resumble) 15 | - [文件操作](#rsop) 16 | - [查看信息](#stat) 17 | - [删除](#delete) 18 | - [问题反馈](#issue) 19 | 20 | 21 | # 初始化 22 | 23 | 初始化工作包括对七牛的API Keys的赋值,如: 24 | ```c# 25 | qiniu.Config.ACCESS_KEY = "IT9iP3J9wdXXYsT1p8ns0gWD-CQOdLvIQuyE0FOK"; 26 | qiniu.Config.SECRET_KEY = "zUCzekBtEqTZ4-WJPCGlBrr2PeyYxsYn98LPaivM"; 27 | ``` 28 | 29 | # 上传文件 30 | 31 | ```c# 32 | QiniuFile qfile = new QiniuFile ("", "", ""); 33 | qfile.Upload(); 34 | ``` 35 | 36 | 一个QiniuFile对象表示一个七牛云空间的文件,初始化QiniuFile提供以下三个参数: 37 | - `bucketName`,七牛云空间名称 38 | - `key`,七牛文件key 39 | - `localfile`,本地文件。该参数为可选,如果要上传本地的文件至七牛云空间,则需要指定此参数的值。 40 | 41 | 注意上传为异步操作,上传的结果由事件通知。 42 | 43 | 44 | ## 上传事件 45 | 46 | 共包括五个事件 47 | 48 | 事件名称 | 说明 49 | :--------- | :--------- 50 | UploadCompleted; | 上传完成 51 | UploadFailed; | 上传失败 52 | UploadProgressChanged; | 上传进度 53 | UploadBlockCompleted; | 上传块完成 54 | UploadBlockFailed; | 上传块失败 55 | 56 | 前三个事件比较容易理解,后两个事件是根据七牛的大文件上传机制衍生出来的,合理利用这两个事件可以完成大文件分块上传结果持久化,从而实现续传。 57 | 58 | 59 | ## 上传结果 60 | 成功上传一个文件后,结果通过事件`uploadCompleted`获取得到,包括文件的`Hash`和`Key`以及从七牛云存储返回的原始字符串(主要考虑到上传凭证中指定了自定义的returnBody)。 61 | 62 | ## 续传 63 | 64 | 类`QiniuResumbleUploadEx`可用于续传,见示例。 65 | 66 | 67 | ## 文件操作 68 | 简单的实现了文件的基本信息获取及删除操作,分别为`Stat`和`Delete` 69 | 70 | # 完整示例 71 | 72 | ```c# 73 | using System; 74 | using System.Collections; 75 | using System.Collections.Generic; 76 | using qiniu; 77 | using System.Threading; 78 | 79 | namespace demo 80 | { 81 | class MainClass 82 | { 83 | public static void Main (string[] args) 84 | { 85 | // 初始化qiniu配置,主要是API Keys 86 | qiniu.Config.ACCESS_KEY = "IT9iP3J9wdXXYsT1p8ns0gWD-CQOdLvIQuyE0FOi"; 87 | qiniu.Config.SECRET_KEY = "zUCzekBtEqTZ4-WJPCGlBrr2PeyYxsYn98LPaivM"; 88 | 89 | /********************************************************************** 90 | 可以用下面的方法从配置文件中初始化 91 | qiniu.Config.InitFromAppConfig (); 92 | **********************************************************************/ 93 | 94 | string localfile = "/Users/icattlecoder/Movies/tzd.rmvb"; 95 | string bucket = "icattlecoder"; 96 | string qiniukey = "tzd.rmvb"; 97 | 98 | //====================================================================== 99 | { 100 | QiniuFile qfile = new QiniuFile (bucket, qiniukey, localfile); 101 | 102 | ResumbleUploadEx puttedCtx = new ResumbleUploadEx (localfile); //续传 103 | 104 | ManualResetEvent done = new ManualResetEvent (false); 105 | qfile.UploadCompleted += (sender, e) => { 106 | Console.WriteLine (e.key); 107 | Console.WriteLine (e.Hash); 108 | done.Set (); 109 | }; 110 | qfile.UploadFailed += (sender, e) => { 111 | Console.WriteLine (e.Error.ToString ()); 112 | puttedCtx.Save(); 113 | done.Set (); 114 | }; 115 | qfile.UploadProgressChanged += (sender, e) => { 116 | int percentage = (int)(100 * e.BytesSent / e.TotalBytes); 117 | Console.Write (percentage); 118 | }; 119 | qfile.UploadBlockCompleted += (sender, e) => { 120 | //上传结果持久化 121 | puttedCtx.Add(e.Index,e.Ctx); 122 | puttedCtx.Save(); 123 | }; 124 | qfile.UploadBlockFailed += (sender, e) => { 125 | // 126 | }; 127 | 128 | //上传为异步操作 129 | //上传本地文件到七牛云存储 130 | qfile.Upload (); 131 | 132 | //如果要续传,调用下面的方法 133 | //qfile.Upload (puttedCtx.PuttedCtx); 134 | 135 | done.WaitOne (); 136 | } 137 | 138 | //====================================================================== 139 | { 140 | 141 | try { 142 | QiniuFile qfile = new QiniuFile (bucket, qiniukey); 143 | QiniuFileInfo finfo = qfile.Stat (); 144 | if (finfo != null) { 145 | qfile.Move("cloudcomment","movetest"); 146 | //删除七牛云空间的文件 147 | //qfile.Delete (); 148 | } 149 | } catch (QiniuWebException e) { 150 | Console.WriteLine (e.Error.HttpCode); 151 | Console.WriteLine (e.Error.ToString ()); 152 | } 153 | } 154 | } 155 | } 156 | } 157 | 158 | ``` 159 | 160 | 161 | # 问题反馈 162 | [https://github.com/icattlecoder/qiniu-csharp-sdk/issues/new](https://github.com/icattlecoder/qiniu-csharp-sdk/issues/new) 163 | 164 | -------------------------------------------------------------------------------- /sdk/QiniuEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using Encoding = System.Text.UTF8Encoding; 5 | namespace qiniu 6 | { 7 | /// 8 | /// Qiniu upload failed event arguments. 9 | /// 10 | public class QiniuUploadFailedEventArgs: EventArgs 11 | { 12 | private Exception error; 13 | 14 | /// 15 | /// Gets the error. 16 | /// 17 | /// The error. 18 | public Exception Error { 19 | get { return error; } 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// E. 26 | public QiniuUploadFailedEventArgs(Exception e){ 27 | this.error = e; 28 | } 29 | } 30 | 31 | /// 32 | /// Qiniu upload completed event arguments. 33 | /// 34 | public class QiniuUploadCompletedEventArgs:EventArgs{ 35 | 36 | /// 37 | /// 上传结果的原始JOSN字符串。 38 | /// 39 | /// The raw string. 40 | public string RawString { get; private set;} 41 | 42 | /// 43 | /// 如果 uptoken 没有指定 ReturnBody,那么返回值是标准的 PutRet 结构 44 | /// 45 | public string Hash { get; private set; } 46 | 47 | /// 48 | /// 如果传入的 key == UNDEFINED_KEY,则服务端返回 key 49 | /// 50 | public string key { get; private set; } 51 | 52 | /// 53 | /// Initializes a new instance of the class. 54 | /// 55 | /// Result. 56 | public QiniuUploadCompletedEventArgs(byte[] result){ 57 | if(result!=null){ 58 | try 59 | { 60 | string json = Encoding.UTF8.GetString(result); 61 | this.RawString = json; 62 | Dictionary dict = JsonConvert.DeserializeObject>(json); 63 | object tmp; 64 | if (dict.TryGetValue("hash", out tmp)) 65 | Hash = (string)tmp; 66 | if (dict.TryGetValue("key", out tmp)) 67 | key = (string)tmp; 68 | } 69 | catch (Exception e) 70 | { 71 | //throw e; 72 | } 73 | } 74 | } 75 | 76 | public QiniuUploadCompletedEventArgs(string result){ 77 | this.RawString = result; 78 | Dictionary dict = JsonConvert.DeserializeObject> (result); 79 | object tmp; 80 | if (dict.TryGetValue ("hash", out tmp)) 81 | Hash = (string)tmp; 82 | if (dict.TryGetValue ("key", out tmp)) 83 | key = (string)tmp; 84 | 85 | } 86 | } 87 | 88 | /// 89 | /// Qiniu upload progress changed event arguments. 90 | /// 91 | public class QiniuUploadProgressChangedEventArgs:EventArgs{ 92 | #region 93 | private long bytesSent; 94 | private long totalBytes; 95 | #endregion 96 | 97 | /// 98 | /// Gets the bytes sent. 99 | /// 100 | /// The bytes sent. 101 | public long BytesSent { 102 | get { return bytesSent; } 103 | } 104 | 105 | /// 106 | /// Gets the total sent. 107 | /// 108 | /// The total sent. 109 | public long TotalBytes { 110 | get { return totalBytes; } 111 | } 112 | 113 | /// 114 | /// Initializes a new instance of the class. 115 | /// 116 | /// Sent. 117 | /// Total. 118 | public QiniuUploadProgressChangedEventArgs(long sent,long total){ 119 | this.bytesSent = sent; 120 | this.totalBytes = total; 121 | } 122 | } 123 | 124 | /// 125 | /// Qiniu upload block completed event arguments. 126 | /// 127 | public class QiniuUploadBlockCompletedEventArgs:EventArgs{ 128 | #region vars 129 | private int index; 130 | private BlkputRet ctx; 131 | #endregion 132 | 133 | /// 134 | /// Gets the index. 135 | /// 136 | /// The index. 137 | public int Index { 138 | get { return index; } 139 | } 140 | 141 | /// 142 | /// Gets the context. 143 | /// 144 | /// The context. 145 | public BlkputRet Ctx { 146 | get { return ctx; } 147 | } 148 | 149 | /// 150 | /// Initializes a new instance of the class. 151 | /// 152 | /// Index. 153 | /// Context. 154 | public QiniuUploadBlockCompletedEventArgs(int index ,BlkputRet ctx){ 155 | this.index = index; 156 | this.ctx = ctx; 157 | } 158 | } 159 | 160 | /// 161 | /// Qiniu upload block failed event arguments. 162 | /// 163 | public class QiniuUploadBlockFailedEventArgs:EventArgs{ 164 | 165 | #region 166 | private int index; 167 | private Exception error; 168 | #endregion 169 | 170 | /// 171 | /// Gets the index. 172 | /// 173 | /// The index. 174 | public int Index { 175 | get { return index; } 176 | } 177 | 178 | /// 179 | /// Gets the error. 180 | /// 181 | /// The error. 182 | public Exception Error { 183 | get { return error; } 184 | } 185 | 186 | /// 187 | /// Initializes a new instance of the class. 188 | /// 189 | /// Index. 190 | /// E. 191 | public QiniuUploadBlockFailedEventArgs(int index,Exception e){ 192 | this.index = index; 193 | this.error = e; 194 | } 195 | } 196 | } 197 | 198 | -------------------------------------------------------------------------------- /sdk/QiniuPutPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using Encoding = System.Text.UTF8Encoding; 4 | 5 | namespace qiniu 6 | { 7 | /// 8 | /// PutPolicy 9 | /// See http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html 10 | /// 11 | [JsonObject(MemberSerialization.OptIn)] 12 | public class PutPolicy 13 | { 14 | private string scope; 15 | private string callBackUrl; 16 | private string callBackBody; 17 | private string returnUrl; 18 | private string returnBody; 19 | private string saveKey; 20 | private int insertOnly; 21 | private int detectMime; 22 | private string mimeLimit; 23 | private long fsizeLimit; 24 | private string persistentOps; 25 | private string persistentNotifyUrl; 26 | private string endUser; 27 | private UInt64 expires = 3600; 28 | private UInt64 deadline = 0; 29 | 30 | /// 31 | /// 一般指文件要上传到的目标存储空间(Bucket)。若为”Bucket”,表示限定只能传到该Bucket(仅限于新增文件);若为”Bucket:Key”,表示限定特定的文件,可修改该文件。 32 | /// 33 | [JsonProperty("scope")] 34 | public string Scope { 35 | get { return scope; } 36 | set { scope = value; } 37 | } 38 | 39 | /// 40 | /// 文件上传成功后,Qiniu-Cloud-Server 向 App-Server 发送POST请求的URL,必须是公网上可以正常进行POST请求并能响应 HTTP Status 200 OK 的有效 URL 41 | /// 42 | [JsonProperty("callBackUrl")] 43 | public string CallBackUrl { 44 | get { return callBackUrl; } 45 | set { callBackUrl = value; } 46 | } 47 | 48 | /// 49 | /// 文件上传成功后,Qiniu-Cloud-Server 向 App-Server 发送POST请求的数据。支持 魔法变量 和 自定义变量,不可与 returnBody 同时使用。 50 | /// 51 | [JsonProperty("callBackBody")] 52 | public string CallBackBody { 53 | get { return callBackBody; } 54 | set { callBackBody = value; } 55 | } 56 | 57 | /// 58 | /// 设置用于浏览器端文件上传成功后,浏览器执行301跳转的URL,一般为 HTML Form 上传时使用。文件上传成功后会跳转到 returnUrl?query_string, query_string 会包含 returnBody 内容。returnUrl 不可与 callbackUrl 同时使用 59 | /// 60 | [JsonProperty("returnUrl")] 61 | public string ReturnUrl { 62 | get { return returnUrl; } 63 | set { returnUrl = value; } 64 | } 65 | 66 | /// 67 | /// 文件上传成功后,自定义从 Qiniu-Cloud-Server 最终返回給终端 App-Client 的数据。支持 魔法变量,不可与 callbackBody 同时使用。 68 | /// 69 | [JsonProperty("returnBody")] 70 | public string ReturnBody { 71 | get { return returnBody; } 72 | set { returnBody = value; } 73 | } 74 | 75 | /// 76 | /// 给上传的文件添加唯一属主标识,特殊场景下非常有用,比如根据终端用户标识给图片或视频打水印 77 | /// 78 | [JsonProperty("endUser")] 79 | public string EndUser { 80 | get { return endUser; } 81 | set { endUser = value; } 82 | } 83 | 84 | /// 85 | /// 定义 uploadToken 的失效时间,Unix时间戳,精确到秒,缺省为 3600 秒 86 | /// 87 | [JsonProperty("deadline")] 88 | public UInt64 Deadline { 89 | get { return deadline; } 90 | } 91 | 92 | /// 93 | /// 可选, Gets or sets the save key. 94 | /// 95 | /// The save key. 96 | [JsonProperty("saveKey")] 97 | public string SaveKey { 98 | get { return saveKey; } 99 | set{ saveKey = value; } 100 | } 101 | 102 | /// 103 | /// 可选。 若非0, 即使Scope为 Bucket:Key 的形式也是insert only. 104 | /// 105 | /// The insert only. 106 | [JsonProperty("insertOnly")] 107 | public int InsertOnly { 108 | get { return insertOnly; } 109 | set{ insertOnly = value; } 110 | } 111 | 112 | /// 113 | /// 可选。若非0, 则服务端根据内容自动确定 MimeType */ 114 | /// 115 | /// The detect MIME. 116 | [JsonProperty("detectMime")] 117 | public int DetectMime { 118 | get { 119 | return detectMime; 120 | } 121 | set{ 122 | detectMime = value; 123 | } 124 | } 125 | 126 | /// 127 | /// 限定用户上传的文件类型 128 | /// 指定本字段值,七牛服务器会侦测文件内容以判断MimeType,再用判断值跟指定值进行匹配,匹配成功则允许上传,匹配失败返回400状态码 129 | /// 示例: 130 | ///1. “image/*“表示只允许上传图片类型 131 | ///2. “image/jpeg;image/png”表示只允许上传jpg和png类型的图片 132 | /// 133 | /// The detect MIME. 134 | [JsonProperty("mimeLimit")] 135 | public string MimeLimit 136 | { 137 | get { return mimeLimit; } 138 | set { mimeLimit = value; } 139 | } 140 | 141 | /// 142 | /// 可选, Gets or sets the fsize limit. 143 | /// 144 | /// The fsize limit. 145 | [JsonProperty("fsizeLimit")] 146 | public long FsizeLimit { 147 | get { return fsizeLimit; } 148 | set{ fsizeLimit = value; } 149 | } 150 | 151 | /// 152 | /// 音视频转码持久化完成后,七牛的服务器会向用户发送处理结果通知。这里指定的url就是用于接收通知的接口。设置了`persistentOps`,则需要同时设置此字段 153 | /// 154 | [JsonProperty("persistentNotifyUrl")] 155 | public string PersistentNotifyUrl { 156 | get { return persistentNotifyUrl; } 157 | set { persistentNotifyUrl = value; } 158 | } 159 | 160 | /// 161 | /// 可指定音视频文件上传完成后,需要进行的转码持久化操作。asyncOps的处理结果保存在缓存当中,有可能失效。而persistentOps的处理结果以文件形式保存在bucket中,体验更佳。[数据处理(持久化)](http://docs.qiniu.com/api/persistent-ops.html 162 | /// 163 | [JsonProperty("persistentOps")] 164 | public string PersistentOps { 165 | get { return persistentOps; } 166 | set { persistentOps = value; } 167 | } 168 | 169 | /// 170 | /// Initializes a new instance of the class. 171 | /// 172 | /// Scope. 173 | /// Expires. 174 | public PutPolicy (string scope, UInt32 expires=3600) 175 | { 176 | this.scope = scope; 177 | this.expires = expires; 178 | } 179 | 180 | /// 181 | /// 生成上传Token 182 | /// 183 | /// 184 | public string Token (MAC mac=null) 185 | { 186 | if (string.IsNullOrEmpty (persistentOps) ^ string.IsNullOrEmpty (persistentNotifyUrl)) { 187 | throw new Exception ("PersistentOps and PersistentNotifyUrl error"); 188 | } 189 | if (string.IsNullOrEmpty (callBackUrl) ^ string.IsNullOrEmpty (callBackBody)) { 190 | throw new Exception ("CallBackUrl and CallBackBody error"); 191 | } 192 | if (!string.IsNullOrEmpty (returnUrl) && !string.IsNullOrEmpty (callBackUrl)) { 193 | throw new Exception ("returnUrl and callBackUrl error"); 194 | } 195 | if (mac == null) { 196 | mac = new MAC (Config.ACCESS_KEY, Config.SECRET_KEY); 197 | } 198 | this.deadline = (UInt32)((DateTime.Now.ToUniversalTime ().Ticks - 621355968000000000) / 10000000 + (long)expires); 199 | string flag = this.ToString (); 200 | return mac.SignWithData (Encoding.UTF8.GetBytes (flag)); 201 | } 202 | 203 | /// 204 | /// Returns a that represents the current in json formmat. 205 | /// 206 | /// A that represents the current . 207 | public override string ToString () 208 | { 209 | return JsonConvert.SerializeObject (this); 210 | } 211 | } 212 | } 213 | 214 | 215 | -------------------------------------------------------------------------------- /sdk/QiniuWebClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Timers; 3 | using System.Threading; 4 | using System.IO; 5 | using System.Collections.Specialized; 6 | using System.Net; 7 | 8 | namespace qiniu 9 | { 10 | public class QiniuWebClient:WebClient 11 | { 12 | public event EventHandler Timeout; 13 | /// 14 | /// Ons the timeout. 15 | /// 16 | protected virtual void onTimeout(){ 17 | if (this.Timeout != null) { 18 | this.Timeout (this,new EventArgs()); 19 | } 20 | } 21 | 22 | private bool isUploading = true; 23 | private object isUploadinglocker = new object (); 24 | 25 | ManualResetEvent done = new ManualResetEvent (false); 26 | 27 | System.Timers.Timer timer; 28 | 29 | private string uptoken; 30 | 31 | /// 32 | /// Gets or sets upload token. 33 | /// 34 | /// Up token. 35 | public string UpToken { 36 | get { return uptoken; } 37 | set { 38 | uptoken = value; 39 | this.Headers.Add ("Authorization", "UpToken " + this.uptoken); 40 | } 41 | } 42 | 43 | /// 44 | /// Initializes a new instance of the class. 45 | /// 46 | /// Timeout. 47 | public QiniuWebClient(double timeout = 100000.0){ 48 | 49 | this.timer = new System.Timers.Timer (timeout); 50 | this.timer.Elapsed+= (object sender, ElapsedEventArgs e) => { 51 | if(!isUploading){ 52 | onTimeout(); 53 | done.Set(); 54 | return; 55 | } 56 | lock(isUploadinglocker){ 57 | isUploading = false; 58 | } 59 | }; 60 | } 61 | 62 | /// 63 | /// Posts the form. 64 | /// 65 | /// URL. 66 | /// Values. 67 | /// Filename. 68 | public void AsyncPostForm(string url,NameValueCollection formData,string filename){ 69 | FileInfo fileInfo = new FileInfo (filename); 70 | string boundary = RandomBoundary (); 71 | using (FileStream fileStream = fileInfo.OpenRead()) 72 | using (Stream postDataStream = GetPostStream (fileStream, formData ["key"], formData, boundary)) { 73 | this.Headers.Add ("Content-Type", "multipart/form-data; boundary=" + boundary); 74 | byte[] hugeBuffer = new byte[postDataStream.Length]; 75 | postDataStream.Seek (0, SeekOrigin.Begin); 76 | postDataStream.Read (hugeBuffer, 0, (int)postDataStream.Length); 77 | UploadDataAsync (new Uri(url), "POST", hugeBuffer); 78 | } 79 | } 80 | 81 | /// 82 | /// Posts the form. 83 | /// 84 | /// URL. 85 | /// Values. 86 | /// Filename. 87 | public byte[] PostForm(string url,NameValueCollection formData,string filename){ 88 | FileInfo fileInfo = new FileInfo (filename); 89 | string boundary = RandomBoundary (); 90 | using (FileStream fileStream = fileInfo.OpenRead()) 91 | using (Stream postDataStream = GetPostStream (fileStream, formData ["key"], formData, boundary)) { 92 | this.Headers.Add ("Content-Type", "multipart/form-data; boundary=" + boundary); 93 | byte[] hugeBuffer = new byte[postDataStream.Length]; 94 | postDataStream.Seek (0, SeekOrigin.Begin); 95 | postDataStream.Read (hugeBuffer, 0, (int)postDataStream.Length); 96 | return UploadData (new Uri(url), "POST", hugeBuffer); 97 | } 98 | } 99 | 100 | /// 101 | /// Call the specified url and mac. 102 | /// 103 | /// URL. 104 | /// Mac. 105 | public string Call(string url,MAC mac){ 106 | try{ 107 | HttpWebRequest req = this.GetWebRequest (new Uri (url)) as HttpWebRequest; 108 | mac.SignRequest (req, null); 109 | using (HttpWebResponse response = req.GetResponse() as HttpWebResponse) { 110 | using (StreamReader reader = new StreamReader(response.GetResponseStream())) { 111 | return reader.ReadToEnd (); 112 | } 113 | } 114 | }catch(Exception e){ 115 | throw e; 116 | } 117 | } 118 | 119 | protected override void OnUploadDataCompleted (UploadDataCompletedEventArgs e) 120 | { 121 | base.OnUploadDataCompleted (e); 122 | done.Set (); 123 | } 124 | 125 | protected override void OnUploadProgressChanged (UploadProgressChangedEventArgs e) 126 | { 127 | lock (isUploadinglocker) { 128 | isUploading = true; 129 | } 130 | base.OnUploadProgressChanged (e); 131 | } 132 | 133 | /// 134 | /// Is the upload data async. 135 | /// 136 | /// The upload data async. 137 | /// URL. 138 | /// Data. 139 | public void iUploadDataAsync(string url,string method,byte[] data){ 140 | 141 | this.UploadDataAsync (new Uri (url),method, data); 142 | this.timer.Start (); 143 | done.WaitOne (); 144 | return; 145 | } 146 | 147 | 148 | private Stream GetPostStream (Stream putStream, string fileName, NameValueCollection formData, string boundary) 149 | { 150 | Stream postDataStream = new System.IO.MemoryStream (); 151 | 152 | //adding form data 153 | 154 | string formDataHeaderTemplate = Environment.NewLine + "--" + boundary + Environment.NewLine + 155 | "Content-Disposition: form-data; name=\"{0}\";" + Environment.NewLine + Environment.NewLine + "{1}"; 156 | 157 | foreach (string key in formData.Keys) { 158 | byte[] formItemBytes = System.Text.Encoding.UTF8.GetBytes (string.Format (formDataHeaderTemplate, 159 | key, formData [key])); 160 | postDataStream.Write (formItemBytes, 0, formItemBytes.Length); 161 | } 162 | 163 | //adding file,Stream data 164 | #region adding file data 165 | 166 | string fileHeaderTemplate = Environment.NewLine + "--" + boundary + Environment.NewLine + 167 | "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" + 168 | Environment.NewLine + "Content-Type: application/octet-stream" + Environment.NewLine + Environment.NewLine; 169 | byte[] fileHeaderBytes = System.Text.Encoding.UTF8.GetBytes (string.Format (fileHeaderTemplate, 170 | "file", fileName)); 171 | postDataStream.Write (fileHeaderBytes, 0, fileHeaderBytes.Length); 172 | 173 | byte[] buffer = new byte[1024*1024]; 174 | int bytesRead = 0; 175 | while ((bytesRead = putStream.Read(buffer, 0, buffer.Length)) != 0) { 176 | postDataStream.Write (buffer, 0, bytesRead); 177 | } 178 | putStream.Close (); 179 | #endregion 180 | 181 | #region adding end 182 | byte[] endBoundaryBytes = System.Text.Encoding.UTF8.GetBytes (Environment.NewLine + "--" + boundary + "--" + Environment.NewLine); 183 | postDataStream.Write (endBoundaryBytes, 0, endBoundaryBytes.Length); 184 | #endregion 185 | 186 | return postDataStream; 187 | 188 | } 189 | 190 | private string RandomBoundary (){ 191 | return String.Format ("----------{0:N}", Guid.NewGuid ()); 192 | } 193 | 194 | /// 195 | /// 196 | /// 197 | /// 198 | protected override void Dispose(bool disposing) 199 | { 200 | if (timer.Enabled) { 201 | timer.Dispose(); 202 | } 203 | base.Dispose(disposing); 204 | } 205 | } 206 | } 207 | 208 | -------------------------------------------------------------------------------- /demo/Base64File.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace demo 5 | { 6 | public class Base64File 7 | { 8 | int fileSize = -1; 9 | string base64Content; 10 | 11 | public int FileSize { 12 | get { 13 | if (this.fileSize < 0) { 14 | this.fileSize=Convert.FromBase64String (this.base64Content).Length; 15 | } 16 | return this.fileSize; 17 | } 18 | } 19 | 20 | public Base64File (string base64Content) 21 | { 22 | this.base64Content = base64Content; 23 | } 24 | 25 | public void Save(string filename){ 26 | byte[] fileContent = Convert.FromBase64String(this.base64Content); 27 | File.WriteAllBytes (filename, fileContent); 28 | } 29 | 30 | public static string TESTBASE64 = "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"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sdk/QiniuFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.IO; 6 | using System.Net; 7 | using System.Collections.Specialized; 8 | using Newtonsoft.Json; 9 | using Encoding = System.Text.UTF8Encoding; 10 | 11 | namespace qiniu 12 | { 13 | public class QiniuFile 14 | { 15 | 16 | #region events 17 | /// 18 | /// Occurs when upload progress changed. 19 | /// 20 | public event EventHandler UploadProgressChanged; 21 | protected void onQiniuUploadProgressChanged(QiniuUploadProgressChangedEventArgs e){ 22 | if (this.UploadProgressChanged != null) { 23 | UploadProgressChanged (this,e); 24 | } 25 | } 26 | 27 | /// 28 | /// Occurs when upload failed. 29 | /// 30 | public event EventHandler UploadFailed; 31 | protected void onUploadFailed(QiniuUploadFailedEventArgs e){ 32 | if (this.UploadFailed != null) { 33 | UploadFailed (this,e); 34 | } 35 | } 36 | 37 | /// 38 | /// Occurs when upload completed. 39 | /// 40 | public event EventHandler UploadCompleted; 41 | protected void onQiniuUploadCompleted(QiniuUploadCompletedEventArgs e){ 42 | if (this.UploadCompleted != null) { 43 | UploadCompleted (this,e); 44 | } 45 | } 46 | 47 | /// 48 | /// Occurs when upload block completed. 49 | /// 50 | public event EventHandler UploadBlockCompleted; 51 | protected void onQiniuUploadBlockCompleted(QiniuUploadBlockCompletedEventArgs e){ 52 | if (this.UploadBlockCompleted != null) { 53 | UploadBlockCompleted (this,e); 54 | } 55 | } 56 | 57 | /// 58 | /// Occurs when upload block failed. 59 | /// 60 | public event EventHandler UploadBlockFailed; 61 | protected void onQiniuUploadBlockFailed(QiniuUploadBlockFailedEventArgs e){ 62 | if (this.UploadBlockFailed != null) { 63 | UploadBlockFailed (this,e); 64 | } 65 | } 66 | 67 | /// 68 | /// Occurs when upload canceled. 69 | /// 70 | public event EventHandler UploadCanceled; 71 | protected void onQiniuUploadCanceld(){ 72 | UploadCanceled (this, null); 73 | } 74 | #endregion 75 | 76 | #region localVars 77 | private string bucketName; 78 | private string key; 79 | private string localfile; 80 | private bool uploading = false; 81 | #endregion 82 | 83 | #region construct 84 | /// 85 | /// Initializes a new instance of the class. 86 | /// 87 | public QiniuFile (string bucketName) 88 | { 89 | this.bucketName = bucketName; 90 | } 91 | 92 | /// 93 | /// Initializes a new instance of the class. 94 | /// 95 | /// Bucket. 96 | /// Key. 97 | public QiniuFile(string bucketName,string key){ 98 | this.bucketName = bucketName; 99 | this.key = key; 100 | } 101 | 102 | /// 103 | /// Initializes a new instance of the class. 104 | /// 105 | /// Bucket. 106 | /// Key. 107 | /// Localfile. 108 | public QiniuFile(string bucketName,string key,string localfile){ 109 | this.bucketName = bucketName; 110 | this.key = key; 111 | if (!File.Exists(localfile)) 112 | { 113 | throw new Exception(string.Format("{0} does not exist", localfile)); 114 | } 115 | this.localfile = localfile; 116 | } 117 | #endregion 118 | 119 | /// 120 | /// Stat the QiniuFile with mac. 121 | /// 122 | /// Mac. 123 | public QiniuFileInfo Stat (MAC mac=null){ 124 | if (mac == null) 125 | mac = new MAC (); 126 | string url = string.Format ("{0}/{1}/{2}", Config.RS_HOST, "stat", Base64URLSafe.Encode (this.bucketName + ":" + this.key)); 127 | try { 128 | using (QiniuWebClient client = new QiniuWebClient ()) { 129 | string result = client.Call (url, mac); 130 | return GetQiniuEntry (result); 131 | } 132 | } catch (WebException e) { 133 | throw new QiniuWebException(e); 134 | }catch(Exception e){ 135 | throw e; 136 | } 137 | } 138 | 139 | /// 140 | /// Delete the QiniuFile with mac. 141 | /// 142 | /// Mac. 143 | public bool Delete(MAC mac=null){ 144 | 145 | if (mac == null) 146 | mac = new MAC (); 147 | string url = string.Format ("{0}/{1}/{2}", Config.RS_HOST, "delete", Base64URLSafe.Encode (this.bucketName + ":" + this.key)); 148 | try { 149 | using (QiniuWebClient client = new QiniuWebClient ()) { 150 | client.Call (url, mac); 151 | return true; 152 | } 153 | } catch (WebException e) { 154 | throw new QiniuWebException(e); 155 | } catch (Exception e) { 156 | throw e; 157 | } 158 | } 159 | 160 | /// 161 | /// Move the specified destBucket, destKey and mac. 162 | /// 163 | /// Destination bucket. 164 | /// Destination key. 165 | /// Mac. 166 | public bool Move(string destBucket,string destKey,MAC mac=null){ 167 | if (mac == null) { 168 | mac = new MAC (); 169 | } 170 | string url = string.Format ("{0}/{1}/{2}/{3}", 171 | Config.RS_HOST, 172 | "move", 173 | Base64URLSafe.Encode (this.bucketName+":"+this.key), 174 | Base64URLSafe.Encode (destBucket+":"+destKey)); 175 | try { 176 | using (QiniuWebClient client = new QiniuWebClient ()) { 177 | client.Call (url, mac); 178 | return true; 179 | } 180 | } catch (WebException e) { 181 | throw new QiniuWebException(e); 182 | } catch (Exception e) { 183 | throw e; 184 | } 185 | } 186 | 187 | /// 188 | /// Uploads the string. 189 | /// 190 | /// Base64 content. 191 | public void UploadString(int filesize,string mimeType,string base64Content){ 192 | string token = getDefalutToken (this.bucketName, this.key); 193 | UploadString (token, filesize, mimeType, base64Content); 194 | } 195 | 196 | /// 197 | /// Uploads the string. 198 | /// 199 | /// Token. 200 | /// Base64 content. 201 | public void UploadString(string token,int fileSize,string mimeType,string base64Content){ 202 | using (QiniuWebClient qwc = new QiniuWebClient ()) { 203 | qwc.UpToken = token; 204 | string url = Config.UP_HOST + 205 | string.Format("/putb64/{0}/key/{1}/mimeType/{2}", 206 | fileSize, 207 | Base64URLSafe.Encode(this.key), 208 | Base64URLSafe.Encode(mimeType)); 209 | 210 | qwc.UploadStringCompleted += (sender, e) => { 211 | if (e.Error != null && e.Error is WebException) { 212 | if (e.Error is WebException) { 213 | QiniuWebException qwe = new QiniuWebException (e.Error as WebException); 214 | onUploadFailed (new QiniuUploadFailedEventArgs (qwe)); 215 | } else { 216 | onUploadFailed (new QiniuUploadFailedEventArgs (e.Error)); 217 | } 218 | } else { 219 | onQiniuUploadCompleted(new QiniuUploadCompletedEventArgs(e.Result)); 220 | 221 | onQiniuUploadCompleted (new QiniuUploadCompletedEventArgs (e.Result)); 222 | } 223 | }; 224 | 225 | qwc.UploadProgressChanged += (sender, e) => { 226 | onQiniuUploadProgressChanged (new QiniuUploadProgressChangedEventArgs (e.BytesSent, e.TotalBytesToSend)); 227 | }; 228 | 229 | qwc.Headers.Add("Content-Type", "application/octet-stream"); 230 | qwc.UploadStringAsync (new Uri (url), "POST", base64Content); 231 | } 232 | } 233 | 234 | /// 235 | /// Asyncs the upload. 236 | /// 237 | public void Upload(BlkputRet[] blkRets=null){ 238 | if (!uploading) { 239 | string token = getDefalutToken (this.bucketName,this.key); 240 | Upload (token,blkRets); 241 | } 242 | } 243 | 244 | /// 245 | /// Upload the specified token. 246 | /// 247 | /// Token. 248 | public void Upload(string token,BlkputRet[] blkRets=null){ 249 | if (uploading) { 250 | return; 251 | } 252 | 253 | FileInfo finfo = new FileInfo (this.localfile); 254 | if (finfo.Length < (long)(1024 * 1024 * 8)) { 255 | uploadSmallFile (token); 256 | } else { 257 | uploadBigFile (token,blkRets); 258 | } 259 | } 260 | 261 | /// 262 | /// Uploads the small file ( < 8MB). 263 | /// 264 | /// Token. 265 | private void uploadSmallFile(string token){ 266 | if (uploading) { 267 | return; 268 | } 269 | uploading = true; 270 | NameValueCollection formData = new NameValueCollection (); 271 | formData ["key"] = this.key; 272 | formData ["token"] = token; 273 | try { 274 | using (QiniuWebClient qwc = new QiniuWebClient ()) { 275 | qwc.UploadDataCompleted += (sender, e) => { 276 | if (e.Error != null && e.Error is WebException) { 277 | if (e.Error is WebException) { 278 | QiniuWebException qwe = new QiniuWebException (e.Error as WebException); 279 | onUploadFailed (new QiniuUploadFailedEventArgs (qwe)); 280 | } else { 281 | onUploadFailed (new QiniuUploadFailedEventArgs (e.Error)); 282 | } 283 | } else { 284 | onQiniuUploadCompleted (new QiniuUploadCompletedEventArgs (e.Result)); 285 | } 286 | }; 287 | qwc.UploadProgressChanged += (sender, e) => { 288 | onQiniuUploadProgressChanged (new QiniuUploadProgressChangedEventArgs (e.BytesSent, e.TotalBytesToSend)); 289 | }; 290 | qwc.AsyncPostForm (Config.UP_HOST, formData, this.localfile); 291 | } 292 | } catch (WebException we) { 293 | onUploadFailed (new QiniuUploadFailedEventArgs (new QiniuWebException (we))); 294 | } catch (Exception e) { 295 | onUploadFailed (new QiniuUploadFailedEventArgs (e)); 296 | } finally { 297 | uploading = false; 298 | } 299 | } 300 | 301 | /// 302 | /// Uploads the big file ( > 8MB ). 303 | /// 304 | /// Token. 305 | private void uploadBigFile(string token,BlkputRet[] puttedBlk=null){ 306 | uploading = true; 307 | Action a = () => { 308 | FileInfo finfo = new FileInfo (this.localfile); 309 | int blockcount = block_count (finfo.Length); 310 | 311 | BlkputRet []blkRets = new BlkputRet[blockcount]; 312 | using (FileStream fs = File.OpenRead (this.localfile)) { 313 | long totalSent = 0; 314 | int readLen = BLOCKSIZE; 315 | byte[] buf = new byte[readLen]; 316 | for (int i = 0; i < blockcount; i++) { 317 | if (puttedBlk!=null&&i< puttedBlk.Length) { 318 | blkRets[i] = puttedBlk[i]; 319 | totalSent +=(long)blkRets[i].offset; 320 | continue; 321 | } 322 | if (i == blockcount - 1) { 323 | readLen = (int)(finfo.Length - i * BLOCKSIZE); 324 | buf = new byte[readLen]; 325 | } 326 | fs.Seek ((long)i * BLOCKSIZE, SeekOrigin.Begin); 327 | fs.Read (buf, 0, readLen); 328 | using (QiniuWebClient client = new QiniuWebClient ()) { 329 | bool failed = false; 330 | client.UploadDataCompleted += (sender, e) => { 331 | if (e.Error != null) { 332 | onQiniuUploadBlockFailed (new QiniuUploadBlockFailedEventArgs (i, e.Error)); 333 | failed =true; 334 | return; 335 | } else { 336 | blkRets [i] = GetBlkPutRet (e.Result); 337 | onQiniuUploadBlockCompleted (new QiniuUploadBlockCompletedEventArgs (i, blkRets [i])); 338 | } 339 | }; 340 | client.UploadProgressChanged += (sender, e) => { 341 | onQiniuUploadProgressChanged (new QiniuUploadProgressChangedEventArgs (totalSent + e.BytesSent, finfo.Length)); 342 | }; 343 | client.Timeout+= (sender, e) => { 344 | onQiniuUploadBlockFailed(new QiniuUploadBlockFailedEventArgs(i,new Exception("QiniuWebClient Timeout."))); 345 | failed = true; 346 | }; 347 | client.UpToken = token; 348 | client.Headers.Add("Content-Type", "application/octet-stream"); 349 | string url = string.Format("{0}/mkblk/{1}", Config.UP_HOST, readLen); 350 | client.iUploadDataAsync ( url, "POST", buf); 351 | if(failed){ 352 | return; 353 | } 354 | totalSent += readLen; 355 | } 356 | } 357 | } 358 | try { 359 | byte[] result = Mkfile (blkRets, token, this.key, finfo.Length); 360 | if (result != null && result.Length > 0) { 361 | onQiniuUploadCompleted (new QiniuUploadCompletedEventArgs (result)); 362 | } 363 | } catch (Exception e) { 364 | onUploadFailed (new QiniuUploadFailedEventArgs (e)); 365 | } 366 | }; 367 | a.BeginInvoke (null, null); 368 | } 369 | 370 | /// 371 | /// Mkfile the specified blkRets, key and fsize. 372 | /// 373 | /// Blk rets. 374 | /// Key. 375 | /// Fsize. 376 | private static byte[] Mkfile(BlkputRet[] blkRets,string token, string key, long fsize) 377 | { 378 | StringBuilder urlBuilder = new StringBuilder(); 379 | urlBuilder.AppendFormat("{0}/mkfile/{1}", Config.UP_HOST, fsize); 380 | if (!string.IsNullOrEmpty(key)) 381 | { 382 | urlBuilder.AppendFormat("/key/{0}", Base64URLSafe.ToBase64URLSafe(key)); 383 | } 384 | int proCount = blkRets.Length; 385 | using (Stream body = new MemoryStream()) 386 | { 387 | for (int i = 0; i < proCount; i++) 388 | { 389 | byte[] bctx = Encoding.ASCII.GetBytes(blkRets[i].ctx); 390 | body.Write(bctx, 0, bctx.Length); 391 | if (i != proCount - 1) 392 | { 393 | body.WriteByte((byte)','); 394 | } 395 | } 396 | body.Seek(0, SeekOrigin.Begin); 397 | byte[] data = new byte[body.Length]; 398 | body.Read (data, 0, (int)body.Length); 399 | using(QiniuWebClient client = new QiniuWebClient ()){ 400 | client.UpToken = token; 401 | client.Headers.Add("Content-Type", "application/octet-stream"); 402 | return client.UploadData (urlBuilder.ToString(), "POST", data); 403 | } 404 | } 405 | } 406 | 407 | #region Config 408 | private const int blockBits = 22; 409 | private readonly static int BLOCKSIZE = 1 << blockBits; 410 | private readonly static int blockMask = BLOCKSIZE - 1; 411 | private static int block_count(long fsize) 412 | { 413 | return (int)((fsize + blockMask) >> blockBits); 414 | } 415 | private static BlkputRet GetBlkPutRet(byte[] result){ 416 | return JsonConvert.DeserializeObject (Encoding.UTF8.GetString (result)); 417 | } 418 | private static QiniuFileInfo GetQiniuEntry(string result){ 419 | return JsonConvert.DeserializeObject (result); 420 | } 421 | private static string getDefalutToken(string bucketName,string key){ 422 | string scope = bucketName; 423 | if(!string.IsNullOrEmpty(key)){ 424 | scope += ":" + key; 425 | } 426 | PutPolicy policy = new PutPolicy (scope, 3600 * 24); 427 | return policy.Token (); 428 | } 429 | #endregion 430 | } 431 | 432 | [JsonObject(MemberSerialization.OptIn)] 433 | public class BlkputRet 434 | { 435 | [JsonProperty("ctx")] 436 | public string ctx; 437 | [JsonProperty("checksum")] 438 | public string checkSum; 439 | [JsonProperty("crc32")] 440 | public UInt32 crc32; 441 | [JsonProperty("offset")] 442 | public ulong offset; 443 | } 444 | 445 | [JsonObject(MemberSerialization.OptIn)] 446 | public class QiniuFileInfo 447 | { 448 | /// 449 | /// 文件的Hash值 450 | /// 451 | /// true if this instance hash; otherwise, false. 452 | [JsonProperty("hash")] 453 | public string Hash; 454 | 455 | /// 456 | /// 文件的大小(单位: 字节) 457 | /// 458 | /// The fsize. 459 | [JsonProperty("fsize")] 460 | public long Fsize; 461 | 462 | /// 463 | /// 文件上传到七牛云的时间(Unix时间戳) 464 | /// 465 | /// The put time. 466 | [JsonProperty("putTime")] 467 | public long PutTime; 468 | 469 | /// 470 | /// 文件的媒体类型,比如"image/gif" 471 | /// 472 | /// The type of the MIME. 473 | [JsonProperty("mimeType")] 474 | public string MimeType; 475 | 476 | /// 477 | /// Gets the customer. 478 | /// 479 | /// The customer. 480 | [JsonProperty("customer")] 481 | public string Customer; 482 | 483 | } 484 | } 485 | 486 | --------------------------------------------------------------------------------