Files
duplicati/Duplicati/Library/Backend/TencentCOS/COSBackend.cs
T
2020-06-27 17:54:12 +08:00

432 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using COSXML;
using COSXML.Auth;
using COSXML.Model.Bucket;
using COSXML.Model.Object;
using COSXML.Model.Tag;
using COSXML.Utils;
using Duplicati.Library.Common.IO;
using Duplicati.Library.Interface;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Duplicati.Library.Backend
{
/// <summary>
/// Tencent COS
/// https://cloud.tencent.com/document/product/436
/// https://cloud.tencent.com/document/product/436/32869
/// </summary>
public class COS : IBackend, IStreamingBackend, IRenameEnabledBackend
{
private static readonly string LOGTAG = Logging.Log.LogTagFromType<COS>();
private const string COS_APP_ID = "cos-app-id";
private const string COS_REGION = "cos-region";
private const string COS_SECRET_ID = "cos-secret-id";
private const string COS_SECRET_KEY = "cos-secret-key";
private const string COS_BUCKET = "cos-bucket";
private const bool COS_USE_SSL = true;
private const int CONNECTION_TIMEOUT_MS = 1000 * 60 * 60;
private const int READ_WRITE_TIMEOUT_MS = 1000 * 60 * 60;
private const int KEY_DURATION_SECOND = 60 * 60;
private readonly CosOptions _cosOptions;
private readonly string m_prefix;
private CosXml _cosXml;
/// <summary>
/// 腾讯云对象存储配置
/// </summary>
public class CosOptions
{
/// <summary>
/// 腾讯云账户的账户标识
/// </summary>
public string Appid { get; set; }
/// <summary>
/// 云 API 密钥 SecretId
/// </summary>
public string SecretId { get; set; }
/// <summary>
/// 云 API 密钥 SecretKey
/// </summary>
public string SecretKey { get; set; }
/// <summary>
/// 存储桶地域 ap-guangzhou ap-hongkong
/// https://cloud.tencent.com/document/product/436/6224
/// </summary>
public string Region { get; set; }
/// <summary>
/// 存储桶,格式:BucketName-APPID
/// </summary>
public string Bucket { get; set; }
/// <summary>
/// 存储桶中路径或子文件夹
/// </summary>
public string Path { get; set; }
}
//static COS()
//{
//}
public COS()
{
}
public COS(string url, Dictionary<string, string> options)
{
_cosOptions = new CosOptions();
var uri = new Utility.Uri(url);
m_prefix = uri.HostAndPath?.Trim()?.Trim('/').Trim('/')?.Trim('\\')?.Trim('\\');
if (!string.IsNullOrEmpty(m_prefix))
{
m_prefix += "/";
_cosOptions.Path = m_prefix;
}
if (options.ContainsKey(COS_APP_ID))
{
_cosOptions.Appid = options[COS_APP_ID];
}
if (options.ContainsKey(COS_REGION))
{
_cosOptions.Region = options[COS_REGION];
}
if (options.ContainsKey(COS_SECRET_ID))
{
_cosOptions.SecretId = options[COS_SECRET_ID];
}
if (options.ContainsKey(COS_SECRET_KEY))
{
_cosOptions.SecretKey = options[COS_SECRET_KEY];
}
if (options.ContainsKey(COS_BUCKET))
{
_cosOptions.Bucket = options[COS_BUCKET];
}
}
CosXml GetCosXml()
{
//初始化 CosXmlConfig
string appid = _cosOptions.Appid;//设置腾讯云账户的账户标识 APPID
string region = _cosOptions.Region; //设置一个默认的存储桶地域
CosXmlConfig config = new CosXmlConfig.Builder()
.SetConnectionTimeoutMs(CONNECTION_TIMEOUT_MS) //设置连接超时时间,单位毫秒,默认45000ms
.SetReadWriteTimeoutMs(READ_WRITE_TIMEOUT_MS) //设置读写超时时间,单位毫秒,默认45000ms
.IsHttps(COS_USE_SSL) //设置默认 HTTPS 请求
.SetAppid(appid) //设置腾讯云账户的账户标识 APPID
.SetRegion(region) //设置一个默认的存储桶地域
//.SetDebugLog(true) //显示日志
.Build(); //创建 CosXmlConfig 对象
//方式1, 永久密钥
string secretId = _cosOptions.SecretId; //"云 API 密钥 SecretId";
string secretKey = _cosOptions.SecretKey; //"云 API 密钥 SecretKey";
long durationSecond = KEY_DURATION_SECOND; //每次请求签名有效时长,单位为秒
//初始化 QCloudCredentialProvider,COS SDK 中提供了3种方式:永久密钥、临时密钥、自定义
QCloudCredentialProvider cosCredentialProvider = new DefaultQCloudCredentialProvider(secretId, secretKey, durationSecond);
CosXml cosXml = new CosXmlServer(config, cosCredentialProvider);
return cosXml;
}
public IEnumerable<IFileEntry> List()
{
bool isTruncated = true;
string filename = null;
while (isTruncated)
{
_cosXml = GetCosXml();
string bucket = _cosOptions.Bucket;
GetBucketRequest request = new GetBucketRequest(bucket);
//设置签名有效时长
request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), 600);
if (!string.IsNullOrEmpty(filename))
request.SetMarker(filename);
//获取 m_prefix/ 下的对象
if (!string.IsNullOrEmpty(m_prefix))
request.SetPrefix(m_prefix);
//执行请求
GetBucketResult result = _cosXml.GetBucket(request);
//bucket的相关信息
ListBucket info = result.listBucket;
isTruncated = result.listBucket.isTruncated;
filename = result.listBucket.nextMarker;
foreach (var item in info.contentsList)
{
var last = DateTime.Parse(item.lastModified);
var fileName = item.key;
if (!string.IsNullOrWhiteSpace(m_prefix))
{
fileName = fileName.Substring(m_prefix.Length);
if (fileName.StartsWith("/", StringComparison.Ordinal))
{
fileName = fileName.Trim('/').Trim('/');
}
}
yield return new FileEntry(fileName, item.size, last, last);
}
}
}
public Task PutAsync(string remotename, string filename, CancellationToken cancelToken)
{
using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
return PutAsync(remotename, fs, cancelToken);
}
public void Get(string remotename, string filename)
{
using (var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.None))
Get(remotename, fs);
}
public void Delete(string remotename)
{
try
{
_cosXml = GetCosXml();
string bucket = _cosOptions.Bucket;
string key = GetFullKey(remotename);
//string bucket = "examplebucket-1250000000"; //存储桶,格式:BucketName-APPID
//string key = "exampleobject"; //对象在存储桶中的位置,即称对象键
DeleteObjectRequest request = new DeleteObjectRequest(bucket, key);
//设置签名有效时长
request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), 600);
//执行请求
DeleteObjectResult result = _cosXml.DeleteObject(request);
//请求成功
Console.WriteLine(result.GetResultInfo());
}
catch (COSXML.CosException.CosClientException clientEx)
{
//请求失败
Console.WriteLine("CosClientException: " + clientEx);
throw;
}
catch (COSXML.CosException.CosServerException serverEx)
{
//请求失败
Console.WriteLine("CosServerException: " + serverEx.GetInfo());
throw;
}
}
public void Test()
{
var json = JsonConvert.SerializeObject(_cosOptions);
try
{
_cosXml = GetCosXml();
string bucket = _cosOptions.Bucket;
HeadBucketRequest request = new HeadBucketRequest(bucket);
request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), 600);
HeadBucketResult result = _cosXml.HeadBucket(request);
Logging.Log.WriteInformationMessage(LOGTAG, "Test", "Request complete {0}: {1}, {2}", result.httpCode, json, result.GetResultInfo());
}
catch (COSXML.CosException.CosClientException clientEx)
{
Logging.Log.WriteErrorMessage(LOGTAG, "Test", clientEx, "Request failed: {0}", json);
throw;
}
catch (COSXML.CosException.CosServerException serverEx)
{
Logging.Log.WriteErrorMessage(LOGTAG, "Test", serverEx, "Request failed: {0}, {1}", json, serverEx.GetInfo());
throw;
}
}
public void CreateFolder()
{
}
public void Dispose()
{
if (_cosXml != null)
_cosXml = null;
}
public Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken)
{
try
{
_cosXml = GetCosXml();
string bucket = _cosOptions.Bucket;
string key = GetFullKey(remotename);
byte[] bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
stream.Seek(0, SeekOrigin.Begin);
PutObjectRequest request = new PutObjectRequest(bucket, key, bytes);
//设置签名有效时长
request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), KEY_DURATION_SECOND);
//执行请求
PutObjectResult result = _cosXml.PutObject(request);
//对象的 eTag
string eTag = result.eTag;
}
catch (COSXML.CosException.CosClientException clientEx)
{
//请求失败
Console.WriteLine("CosClientException: " + clientEx);
throw;
}
catch (COSXML.CosException.CosServerException serverEx)
{
//请求失败
Console.WriteLine("CosServerException: " + serverEx.GetInfo());
throw;
}
return Task.CompletedTask;
}
public void Get(string remotename, Stream stream)
{
//下载返回 bytes 数据
try
{
CosXml cosXml = GetCosXml();
string bucket = _cosOptions.Bucket;
string key = GetFullKey(remotename);
GetObjectBytesRequest request = new GetObjectBytesRequest(bucket, key);
//设置签名有效时长
request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), KEY_DURATION_SECOND);
//执行请求
GetObjectBytesResult result = cosXml.GetObject(request);
//获取内容
byte[] bytes = result.content;
Stream ms = new MemoryStream(bytes);
Utility.Utility.CopyStream(ms, stream);
//请求成功
Console.WriteLine(result.GetResultInfo());
}
catch (COSXML.CosException.CosClientException clientEx)
{
//请求失败
Console.WriteLine("CosClientException: " + clientEx);
throw;
}
catch (COSXML.CosException.CosServerException serverEx)
{
//请求失败
Console.WriteLine("CosServerException: " + serverEx.GetInfo());
throw;
}
}
public void Rename(string oldname, string newname)
{
CosXml cosXml = GetCosXml();
try
{
string sourceAppid = _cosOptions.Appid; //账号 appid
string sourceBucket = _cosOptions.Bucket; //"源对象所在的存储桶
string sourceRegion = _cosOptions.Region; //源对象的存储桶所在的地域
string sourceKey = GetFullKey(oldname); //源对象键
//构造源对象属性
CopySourceStruct copySource = new CopySourceStruct(sourceAppid, sourceBucket,
sourceRegion, sourceKey);
string bucket = _cosOptions.Bucket; //存储桶,格式:BucketName-APPID
string key = GetFullKey(newname); //对象在存储桶中的位置,即称对象键
CopyObjectRequest request = new CopyObjectRequest(bucket, key);
//设置签名有效时长
request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), KEY_DURATION_SECOND);
//设置拷贝源
request.SetCopySource(copySource);
//设置是否拷贝还是更新,此处是拷贝
request.SetCopyMetaDataDirective(COSXML.Common.CosMetaDataDirective.COPY);
//执行请求
CopyObjectResult result = cosXml.CopyObject(request);
//请求成功
Console.WriteLine(result.GetResultInfo());
Logging.Log.WriteInformationMessage(LOGTAG, "COS rename ok", $"cos copy: {key}, " + result.GetResultInfo());
// 删除
Delete(oldname);
}
catch (COSXML.CosException.CosClientException clientEx)
{
//请求失败
Console.WriteLine("CosClientException: " + clientEx);
throw;
}
catch (COSXML.CosException.CosServerException serverEx)
{
//请求失败
Console.WriteLine("CosServerException: " + serverEx.GetInfo());
throw;
}
}
public string DisplayName => Strings.COSBackend.DisplayName;
public string Description => Strings.COSBackend.Description;
public string ProtocolKey => "cos";
public IList<ICommandLineArgument> SupportedCommands
{
get
{
return new List<ICommandLineArgument>(new ICommandLineArgument[] {
new CommandLineArgument(COS_APP_ID, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSAccountDescriptionShort,Strings.COSBackend.COSAccountDescriptionLong),
new CommandLineArgument(COS_REGION, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSLocationDescriptionShort,Strings.COSBackend.COSLocationDescriptionLong),
new CommandLineArgument(COS_SECRET_ID, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSAPISecretIdDescriptionShort,Strings.COSBackend.COSAPISecretIdDescriptionLong),
new CommandLineArgument(COS_SECRET_KEY, CommandLineArgument.ArgumentType.Password, Strings.COSBackend.COSAPISecretKeyDescriptionShort,Strings.COSBackend.COSAPISecretKeyDescriptionLong),
new CommandLineArgument(COS_BUCKET, CommandLineArgument.ArgumentType.String,Strings.COSBackend.COSBucketDescriptionShort,Strings.COSBackend.COSBucketDescriptionLong),
});
}
}
public string[] DNSName => null;
private string GetFullKey(string name)
{
if (string.IsNullOrWhiteSpace(m_prefix))
return name;
return m_prefix + name;
}
}
}