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

420 lines
13 KiB
Go
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.

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)
}
//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)
}