packer-cn/vendor/github.com/ufilesdk-dev/ufile-gosdk/file.go

420 lines
13 KiB
Go
Raw Normal View History

2019-10-12 04:46:21 -04:00
package ufsdk
import (
"bytes"
"encoding/base64"
"crypto/md5"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
const (
fourMegabyte = 1 << 22 //4M
)
//FileDataSet 用于 FileListResponse 里面的 DataSet 字段。
type FileDataSet struct {
BucketName string `json:"BucketName,omitempty"`
FileName string `json:"FileName,omitempty"`
Hash string `json:"Hash,omitempty"`
MimeType string `json:"MimeType,omitempty"`
FirstObject string `json:"first_object,omitempty"`
Size int `json:"Size,omitempty"`
CreateTime int `json:"CreateTime,omitempty"`
ModifyTime int `json:"ModifyTime,omitempty"`
StorageClass string `json:"StorageClass,omitempty"`
RestoreStatus string `json:"RestoreStatus,omitempty"`
}
//FileListResponse 用 PrefixFileList 接口返回的 list 数据。
type FileListResponse struct {
BucketName string `json:"BucketName,omitempty"`
BucketID string `json:"BucketId,omitempty"`
NextMarker string `json:"NextMarker,omitempty"`
DataSet []FileDataSet `json:"DataSet,omitempty"`
}
func (f FileListResponse) String() string {
return structPrettyStr(f)
}
//UploadHit 文件秒传,它的原理是计算出文件的 etag 值与远端服务器进行对比,如果文件存在就快速返回。
func (u *UFileRequest) UploadHit(filePath, keyName string) (err error) {
file, err := openFile(filePath)
if err != nil {
return err
}
defer file.Close()
fsize := getFileSize(file)
etag := calculateEtag(file)
query := &url.Values{}
query.Add("Hash", etag)
query.Add("FileName", keyName)
query.Add("FileSize", strconv.FormatInt(fsize, 10))
reqURL := u.genFileURL("uploadhit") + "?" + query.Encode()
req, err := http.NewRequest("POST", reqURL, nil)
if err != nil {
return err
}
authorization := u.Auth.Authorization("POST", u.BucketName, keyName, req.Header)
req.Header.Add("authorization", authorization)
return u.request(req)
}
//PostFile 使用 HTTP Form 的方式上传一个文件。
//注意:使用本接口上传文件后,调用 UploadHit 接口会返回 404因为经过 form 包装的文件etag 值会不一样,所以会调用失败。
//mimeType 如果为空的话,会调用 net/http 里面的 DetectContentType 进行检测。
//keyName 表示传到 ufile 的文件名。
//小于 100M 的文件推荐使用本接口上传。
func (u *UFileRequest) PostFile(filePath, keyName, mimeType string) (err error) {
file, err := openFile(filePath)
if err != nil {
return err
}
defer file.Close()
h := make(http.Header)
for k, v := range u.RequestHeader {
for i := 0; i < len(v); i++ {
h.Add(k, v[i])
}
}
if mimeType == "" {
mimeType = getMimeType(file)
}
h.Add("Content-Type", mimeType)
authorization := u.Auth.Authorization("POST", u.BucketName, keyName, h)
boundry := makeBoundry()
body := makeFormBody(authorization, boundry, keyName, mimeType, u.verifyUploadMD5, file)
//lastline 一定要写,否则后端解析不到。
lastline := fmt.Sprintf("\r\n--%s--\r\n", boundry)
body.Write([]byte(lastline))
reqURL := u.genFileURL("")
req, err := http.NewRequest("POST", reqURL, body)
if err != nil {
return err
}
req.Header.Add("Content-Type", "multipart/form-data; boundary="+boundry)
contentLength := body.Len()
req.Header.Add("Content-Length", strconv.Itoa(contentLength))
for k, v := range u.RequestHeader {
for i := 0; i < len(v); i++ {
req.Header.Add(k, v[i])
}
}
return u.request(req)
}
//PutFile 把文件直接放到 HTTP Body 里面上传,相对 PostFile 接口,这个要更简单,速度会更快(因为不用包装 form
//mimeType 如果为空的,会调用 net/http 里面的 DetectContentType 进行检测。
//keyName 表示传到 ufile 的文件名。
//小于 100M 的文件推荐使用本接口上传。
func (u *UFileRequest) PutFile(filePath, keyName, mimeType string) error {
reqURL := u.genFileURL(keyName)
file, err := openFile(filePath)
if err != nil {
return err
}
defer file.Close()
b, err := ioutil.ReadAll(file)
if err != nil {
return err
}
req, err := http.NewRequest("PUT", reqURL, bytes.NewBuffer(b))
if err != nil {
return err
}
if mimeType == "" {
mimeType = getMimeType(file)
}
req.Header.Add("Content-Type", mimeType)
for k, v := range u.RequestHeader {
for i := 0; i < len(v); i++ {
req.Header.Add(k, v[i])
}
}
if u.verifyUploadMD5 {
md5Str := fmt.Sprintf("%x", md5.Sum(b))
req.Header.Add("Content-MD5", md5Str)
}
authorization := u.Auth.Authorization("PUT", u.BucketName, keyName, req.Header)
req.Header.Add("authorization", authorization)
fileSize := getFileSize(file)
req.Header.Add("Content-Length", strconv.FormatInt(fileSize, 10))
return u.request(req)
}
//PutFile 把文件直接放到 HTTP Body 里面上传,相对 PostFile 接口,这个要更简单,速度会更快(因为不用包装 form
//mimeType 如果为空的,会调用 net/http 里面的 DetectContentType 进行检测。
//keyName 表示传到 ufile 的文件名。
//小于 100M 的文件推荐使用本接口上传。
//支持带上传回调的参数, policy_json 为json 格式字符串
func (u *UFileRequest) PutFileWithPolicy(filePath, keyName, mimeType string, policy_json string) error {
reqURL := u.genFileURL(keyName)
file, err := openFile(filePath)
if err != nil {
return err
}
defer file.Close()
b, err := ioutil.ReadAll(file)
if err != nil {
return err
}
req, err := http.NewRequest("PUT", reqURL, bytes.NewBuffer(b))
if err != nil {
return err
}
if mimeType == "" {
mimeType = getMimeType(file)
}
req.Header.Add("Content-Type", mimeType)
if u.verifyUploadMD5 {
md5Str := fmt.Sprintf("%x", md5.Sum(b))
req.Header.Add("Content-MD5", md5Str)
}
policy := base64.URLEncoding.EncodeToString([]byte(policy_json))
authorization := u.Auth.AuthorizationPolicy("PUT", u.BucketName, keyName, policy, req.Header)
req.Header.Add("authorization", authorization)
fileSize := getFileSize(file)
req.Header.Add("Content-Length", strconv.FormatInt(fileSize, 10))
return u.request(req)
}
//DeleteFile 删除一个文件,如果删除成功 statuscode 会返回 204否则会返回 404 表示文件不存在。
//keyName 表示传到 ufile 的文件名。
func (u *UFileRequest) DeleteFile(keyName string) error {
reqURL := u.genFileURL(keyName)
req, err := http.NewRequest("DELETE", reqURL, nil)
if err != nil {
return err
}
authorization := u.Auth.Authorization("DELETE", u.BucketName, keyName, req.Header)
req.Header.Add("authorization", authorization)
return u.request(req)
}
//HeadFile 获取一个文件的基本信息,返回的信息全在 header 里面。包含 mimeType, content-length文件大小, etag, Last-Modified:。
//keyName 表示传到 ufile 的文件名。
func (u *UFileRequest) HeadFile(keyName string) error {
reqURL := u.genFileURL(keyName)
req, err := http.NewRequest("HEAD", reqURL, nil)
if err != nil {
return err
}
authorization := u.Auth.Authorization("HEAD", u.BucketName, keyName, req.Header)
req.Header.Add("authorization", authorization)
return u.request(req)
}
//PrefixFileList 获取文件列表。
//prefix 表示匹配文件前缀。
//marker 标志字符串
//limit 列表数量限制,传 0 会默认设置为 20.
func (u *UFileRequest) PrefixFileList(prefix, marker string, limit int) (list FileListResponse, err error) {
query := &url.Values{}
query.Add("prefix", prefix)
query.Add("marker", marker)
if limit == 0 {
limit = 20
}
query.Add("limit", strconv.Itoa(limit))
reqURL := u.genFileURL("") + "?list&" + query.Encode()
req, err := http.NewRequest("GET", reqURL, nil)
if err != nil {
return
}
authorization := u.Auth.Authorization("GET", u.BucketName, "", req.Header)
req.Header.Add("authorization", authorization)
err = u.request(req)
if err != nil {
return
}
err = json.Unmarshal(u.LastResponseBody, &list)
return
}
//GetPublicURL 获取公有空间的文件下载 URL
//keyName 表示传到 ufile 的文件名。
func (u *UFileRequest) GetPublicURL(keyName string) string {
return u.genFileURL(keyName)
}
//GetPrivateURL 获取私有空间的文件下载 URL。
//keyName 表示传到 ufile 的文件名。
//expiresDuation 表示下载链接的过期时间从现在算起24 * time.Hour 表示过期时间为一天。
func (u *UFileRequest) GetPrivateURL(keyName string, expiresDuation time.Duration) string {
t := time.Now()
t = t.Add(expiresDuation)
expires := strconv.FormatInt(t.Unix(), 10)
signature, publicKey := u.Auth.AuthorizationPrivateURL("GET", u.BucketName, keyName, expires, http.Header{})
query := url.Values{}
query.Add("UCloudPublicKey", publicKey)
query.Add("Signature", signature)
query.Add("Expires", expires)
reqURL := u.genFileURL(keyName)
return reqURL + "?" + query.Encode()
}
//Download 把文件下载到 HTTP Body 里面,这里只能用来下载小文件,建议使用 DownloadFile 来下载大文件。
func (u *UFileRequest) Download(reqURL string) error {
req, err := http.NewRequest("GET", reqURL, nil)
if err != nil {
return err
}
return u.request(req)
}
//Download 文件下载接口,下载前会先获取文件大小,如果小于 4M 直接下载。大于 4M 每次会按 4M 的分片来下载。
func (u *UFileRequest) DownloadFile(writer io.Writer, keyName string) error {
err := u.HeadFile(keyName)
if err != nil {
return err
}
size := u.LastResponseHeader.Get("Content-Length")
fileSize, err := strconv.ParseInt(size, 10, 0)
if err != nil || fileSize <= 0 {
return fmt.Errorf("Parse content-lengt returned error")
}
reqURL := u.GetPrivateURL(keyName, 24*time.Hour)
req, err := http.NewRequest("GET", reqURL, nil)
if err != nil {
return err
}
if fileSize < fourMegabyte {
err = u.request(req)
if err != nil {
return err
}
writer.Write(u.LastResponseBody)
} else {
var i int64
for i = 0; i < fileSize; i += fourMegabyte { // 一次下载 4 M
start := i
end := i + fourMegabyte - 1 //数组是从 0 开始的。 &_& .....
if end > fileSize {
end = fileSize
}
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
err = u.request(req)
if err != nil {
return err
}
writer.Write(u.LastResponseBody)
}
}
return nil
}
//CompareFileEtag 检查远程文件的 etag 和本地文件的 etag 是否一致
func (u *UFileRequest) CompareFileEtag(remoteKeyName, localFilePath string) bool {
err := u.HeadFile(remoteKeyName)
if err != nil {
return false
}
remoteEtag := strings.Trim(u.LastResponseHeader.Get("Etag"), "\"")
localEtag := GetFileEtag(localFilePath)
return remoteEtag == localEtag
}
func (u *UFileRequest) genFileURL(keyName string) string {
return u.baseURL.String() + keyName
}
//Restore 用于解冻冷存类型的文件
func (u *UFileRequest) Restore(keyName string) (err error) {
reqURL := u.genFileURL(keyName) + "?restore"
req, err := http.NewRequest("PUT", reqURL, nil)
if err != nil {
return err
}
authorization := u.Auth.Authorization("PUT", u.BucketName, keyName, req.Header)
req.Header.Add("authorization", authorization)
return u.request(req)
}
//ClassSwitch 存储类型转换接口
//keyName 文件名称
//storageClass 所要转换的新文件存储类型,目前支持的类型分别是标准:"STANDARD"、低频:"IA"、冷存:"ARCHIVE"
func (u *UFileRequest) ClassSwitch(keyName string, storageClass string) (err error) {
query := &url.Values{}
query.Add("storageClass", storageClass)
reqURL := u.genFileURL(keyName) + "?" + query.Encode()
req, err := http.NewRequest("PUT", reqURL, nil)
if err != nil {
return err
}
authorization := u.Auth.Authorization("PUT", u.BucketName, keyName, req.Header)
req.Header.Add("authorization", authorization)
return u.request(req)
}
2019-10-30 13:48:41 -04:00
//Rename 重命名指定文件
//keyName 需要被重命名的源文件
//newKeyName 修改后的新文件名
//force 如果已存在同名文件,值为"true"则覆盖,否则会操作失败
func (u *UFileRequest) Rename(keyName, newKeyName, force string) (err error) {
query := url.Values{}
query.Add("newFileName", newKeyName)
query.Add("force", force)
reqURL := u.genFileURL(keyName) + "?" + query.Encode()
req, err := http.NewRequest("PUT", reqURL, nil)
if err != nil {
return err
}
authorization := u.Auth.Authorization("PUT", u.BucketName, keyName, req.Header)
req.Header.Add("authorization", authorization)
return u.request(req)
}
//Copy 从同组织下的源Bucket中拷贝指定文件到目的Bucket中并以新文件名命名
//dstkeyName 拷贝到目的Bucket后的新文件名
//srcBucketName 待拷贝文件所在的源Bucket名称
//srcKeyName 待拷贝文件名称
func (u *UFileRequest) Copy(dstkeyName, srcBucketName, srcKeyName string) (err error) {
reqURL := u.genFileURL(dstkeyName)
req, err := http.NewRequest("PUT", reqURL, nil)
if err != nil {
return err
}
req.Header.Add("X-Ufile-Copy-Source", "/" + srcBucketName + "/" + srcKeyName)
authorization := u.Auth.Authorization("PUT", u.BucketName, dstkeyName, req.Header)
req.Header.Add("authorization", authorization)
return u.request(req)
}