mirror of https://github.com/Xhofe/alist
653 lines
18 KiB
Go
653 lines
18 KiB
Go
package pikpak
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/md5"
|
||
"crypto/sha1"
|
||
"encoding/hex"
|
||
"fmt"
|
||
"github.com/alist-org/alist/v3/internal/driver"
|
||
"github.com/alist-org/alist/v3/internal/model"
|
||
"github.com/alist-org/alist/v3/internal/op"
|
||
"github.com/alist-org/alist/v3/pkg/utils"
|
||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||
jsoniter "github.com/json-iterator/go"
|
||
"github.com/pkg/errors"
|
||
"io"
|
||
"net/http"
|
||
"path/filepath"
|
||
"regexp"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/alist-org/alist/v3/drivers/base"
|
||
"github.com/go-resty/resty/v2"
|
||
)
|
||
|
||
var AndroidAlgorithms = []string{
|
||
"7xOq4Z8s",
|
||
"QE9/9+IQco",
|
||
"WdX5J9CPLZp",
|
||
"NmQ5qFAXqH3w984cYhMeC5TJR8j",
|
||
"cc44M+l7GDhav",
|
||
"KxGjo/wHB+Yx8Lf7kMP+/m9I+",
|
||
"wla81BUVSmDkctHDpUT",
|
||
"c6wMr1sm1WxiR3i8LDAm3W",
|
||
"hRLrEQCFNYi0PFPV",
|
||
"o1J41zIraDtJPNuhBu7Ifb/q3",
|
||
"U",
|
||
"RrbZvV0CTu3gaZJ56PVKki4IeP",
|
||
"NNuRbLckJqUp1Do0YlrKCUP",
|
||
"UUwnBbipMTvInA0U0E9",
|
||
"VzGc",
|
||
}
|
||
|
||
var WebAlgorithms = []string{
|
||
"fyZ4+p77W1U4zcWBUwefAIFhFxvADWtT1wzolCxhg9q7etmGUjXr",
|
||
"uSUX02HYJ1IkyLdhINEFcCf7l2",
|
||
"iWt97bqD/qvjIaPXB2Ja5rsBWtQtBZZmaHH2rMR41",
|
||
"3binT1s/5a1pu3fGsN",
|
||
"8YCCU+AIr7pg+yd7CkQEY16lDMwi8Rh4WNp5",
|
||
"DYS3StqnAEKdGddRP8CJrxUSFh",
|
||
"crquW+4",
|
||
"ryKqvW9B9hly+JAymXCIfag5Z",
|
||
"Hr08T/NDTX1oSJfHk90c",
|
||
"i",
|
||
}
|
||
|
||
var PCAlgorithms = []string{
|
||
"KHBJ07an7ROXDoK7Db",
|
||
"G6n399rSWkl7WcQmw5rpQInurc1DkLmLJqE",
|
||
"JZD1A3M4x+jBFN62hkr7VDhkkZxb9g3rWqRZqFAAb",
|
||
"fQnw/AmSlbbI91Ik15gpddGgyU7U",
|
||
"/Dv9JdPYSj3sHiWjouR95NTQff",
|
||
"yGx2zuTjbWENZqecNI+edrQgqmZKP",
|
||
"ljrbSzdHLwbqcRn",
|
||
"lSHAsqCkGDGxQqqwrVu",
|
||
"TsWXI81fD1",
|
||
"vk7hBjawK/rOSrSWajtbMk95nfgf3",
|
||
}
|
||
|
||
const (
|
||
OSSUserAgent = "aliyun-sdk-android/2.9.13(Linux/Android 14/M2004j7ac;UKQ1.231108.001)"
|
||
OssSecurityTokenHeaderName = "X-OSS-Security-Token"
|
||
ThreadsNum = 10
|
||
)
|
||
|
||
const (
|
||
AndroidClientID = "YNxT9w7GMdWvEOKa"
|
||
AndroidClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
|
||
AndroidClientVersion = "1.49.3"
|
||
AndroidPackageName = "com.pikcloud.pikpak"
|
||
AndroidSdkVersion = "2.0.4.204101"
|
||
WebClientID = "YUMx5nI8ZU8Ap8pm"
|
||
WebClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
|
||
WebClientVersion = "undefined"
|
||
WebPackageName = "drive.mypikpak.com"
|
||
WebSdkVersion = "8.0.3"
|
||
PCClientID = "YvtoWO6GNHiuCl7x"
|
||
PCClientSecret = "1NIH5R1IEe2pAxZE3hv3uA"
|
||
PCClientVersion = "undefined" // 2.5.6.4831
|
||
PCPackageName = "mypikpak.com"
|
||
PCSdkVersion = "8.0.3"
|
||
)
|
||
|
||
func (d *PikPak) login() error {
|
||
// 检查用户名和密码是否为空
|
||
if d.Addition.Username == "" || d.Addition.Password == "" {
|
||
return errors.New("username or password is empty")
|
||
}
|
||
|
||
url := "https://user.mypikpak.net/v1/auth/signin"
|
||
// 使用 用户填写的 CaptchaToken —————— (验证后的captcha_token)
|
||
if d.GetCaptchaToken() == "" {
|
||
if err := d.RefreshCaptchaTokenInLogin(GetAction(http.MethodPost, url), d.Username); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
var e ErrResp
|
||
res, err := base.RestyClient.SetRetryCount(1).R().SetError(&e).SetBody(base.Json{
|
||
"captcha_token": d.GetCaptchaToken(),
|
||
"client_id": d.ClientID,
|
||
"client_secret": d.ClientSecret,
|
||
"username": d.Username,
|
||
"password": d.Password,
|
||
}).SetQueryParam("client_id", d.ClientID).Post(url)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if e.ErrorCode != 0 {
|
||
return &e
|
||
}
|
||
data := res.Body()
|
||
d.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
|
||
d.AccessToken = jsoniter.Get(data, "access_token").ToString()
|
||
d.Common.SetUserID(jsoniter.Get(data, "sub").ToString())
|
||
return nil
|
||
}
|
||
|
||
func (d *PikPak) refreshToken(refreshToken string) error {
|
||
url := "https://user.mypikpak.net/v1/auth/token"
|
||
var e ErrResp
|
||
res, err := base.RestyClient.SetRetryCount(1).R().SetError(&e).
|
||
SetHeader("user-agent", "").SetBody(base.Json{
|
||
"client_id": d.ClientID,
|
||
"client_secret": d.ClientSecret,
|
||
"grant_type": "refresh_token",
|
||
"refresh_token": refreshToken,
|
||
}).SetQueryParam("client_id", d.ClientID).Post(url)
|
||
if err != nil {
|
||
d.Status = err.Error()
|
||
op.MustSaveDriverStorage(d)
|
||
return err
|
||
}
|
||
if e.ErrorCode != 0 {
|
||
if e.ErrorCode == 4126 {
|
||
// 1. 未填写 username 或 password
|
||
if d.Addition.Username == "" || d.Addition.Password == "" {
|
||
return errors.New("refresh_token invalid, please re-provide refresh_token")
|
||
} else {
|
||
// refresh_token invalid, re-login
|
||
return d.login()
|
||
}
|
||
}
|
||
d.Status = e.Error()
|
||
op.MustSaveDriverStorage(d)
|
||
return errors.New(e.Error())
|
||
}
|
||
data := res.Body()
|
||
d.Status = "work"
|
||
d.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
|
||
d.AccessToken = jsoniter.Get(data, "access_token").ToString()
|
||
d.Common.SetUserID(jsoniter.Get(data, "sub").ToString())
|
||
d.Addition.RefreshToken = d.RefreshToken
|
||
op.MustSaveDriverStorage(d)
|
||
return nil
|
||
}
|
||
|
||
func (d *PikPak) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||
req := base.RestyClient.R()
|
||
req.SetHeaders(map[string]string{
|
||
//"Authorization": "Bearer " + d.AccessToken,
|
||
"User-Agent": d.GetUserAgent(),
|
||
"X-Device-ID": d.GetDeviceID(),
|
||
"X-Captcha-Token": d.GetCaptchaToken(),
|
||
})
|
||
if d.AccessToken != "" {
|
||
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
||
}
|
||
|
||
if callback != nil {
|
||
callback(req)
|
||
}
|
||
if resp != nil {
|
||
req.SetResult(resp)
|
||
}
|
||
var e ErrResp
|
||
req.SetError(&e)
|
||
res, err := req.Execute(method, url)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
switch e.ErrorCode {
|
||
case 0:
|
||
return res.Body(), nil
|
||
case 4122, 4121, 16:
|
||
// access_token 过期
|
||
if err1 := d.refreshToken(d.RefreshToken); err1 != nil {
|
||
return nil, err1
|
||
}
|
||
return d.request(url, method, callback, resp)
|
||
case 9: // 验证码token过期
|
||
if err = d.RefreshCaptchaTokenAtLogin(GetAction(method, url), d.GetUserID()); err != nil {
|
||
return nil, err
|
||
}
|
||
return d.request(url, method, callback, resp)
|
||
case 10: // 操作频繁
|
||
return nil, errors.New(e.ErrorDescription)
|
||
default:
|
||
return nil, errors.New(e.Error())
|
||
}
|
||
}
|
||
|
||
func (d *PikPak) getFiles(id string) ([]File, error) {
|
||
res := make([]File, 0)
|
||
pageToken := "first"
|
||
for pageToken != "" {
|
||
if pageToken == "first" {
|
||
pageToken = ""
|
||
}
|
||
query := map[string]string{
|
||
"parent_id": id,
|
||
"thumbnail_size": "SIZE_LARGE",
|
||
"with_audit": "true",
|
||
"limit": "100",
|
||
"filters": `{"phase":{"eq":"PHASE_TYPE_COMPLETE"},"trashed":{"eq":false}}`,
|
||
"page_token": pageToken,
|
||
}
|
||
var resp Files
|
||
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files", http.MethodGet, func(req *resty.Request) {
|
||
req.SetQueryParams(query)
|
||
}, &resp)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
pageToken = resp.NextPageToken
|
||
res = append(res, resp.Files...)
|
||
}
|
||
return res, nil
|
||
}
|
||
|
||
func GetAction(method string, url string) string {
|
||
urlpath := regexp.MustCompile(`://[^/]+((/[^/\s?#]+)*)`).FindStringSubmatch(url)[1]
|
||
return method + ":" + urlpath
|
||
}
|
||
|
||
type Common struct {
|
||
client *resty.Client
|
||
CaptchaToken string
|
||
UserID string
|
||
// 必要值,签名相关
|
||
ClientID string
|
||
ClientSecret string
|
||
ClientVersion string
|
||
PackageName string
|
||
Algorithms []string
|
||
DeviceID string
|
||
UserAgent string
|
||
// 验证码token刷新成功回调
|
||
RefreshCTokenCk func(token string)
|
||
}
|
||
|
||
func generateDeviceSign(deviceID, packageName string) string {
|
||
|
||
signatureBase := fmt.Sprintf("%s%s%s%s", deviceID, packageName, "1", "appkey")
|
||
|
||
sha1Hash := sha1.New()
|
||
sha1Hash.Write([]byte(signatureBase))
|
||
sha1Result := sha1Hash.Sum(nil)
|
||
|
||
sha1String := hex.EncodeToString(sha1Result)
|
||
|
||
md5Hash := md5.New()
|
||
md5Hash.Write([]byte(sha1String))
|
||
md5Result := md5Hash.Sum(nil)
|
||
|
||
md5String := hex.EncodeToString(md5Result)
|
||
|
||
deviceSign := fmt.Sprintf("div101.%s%s", deviceID, md5String)
|
||
|
||
return deviceSign
|
||
}
|
||
|
||
func BuildCustomUserAgent(deviceID, clientID, appName, sdkVersion, clientVersion, packageName, userID string) string {
|
||
deviceSign := generateDeviceSign(deviceID, packageName)
|
||
var sb strings.Builder
|
||
|
||
sb.WriteString(fmt.Sprintf("ANDROID-%s/%s ", appName, clientVersion))
|
||
sb.WriteString("protocolVersion/200 ")
|
||
sb.WriteString("accesstype/ ")
|
||
sb.WriteString(fmt.Sprintf("clientid/%s ", clientID))
|
||
sb.WriteString(fmt.Sprintf("clientversion/%s ", clientVersion))
|
||
sb.WriteString("action_type/ ")
|
||
sb.WriteString("networktype/WIFI ")
|
||
sb.WriteString("sessionid/ ")
|
||
sb.WriteString(fmt.Sprintf("deviceid/%s ", deviceID))
|
||
sb.WriteString("providername/NONE ")
|
||
sb.WriteString(fmt.Sprintf("devicesign/%s ", deviceSign))
|
||
sb.WriteString("refresh_token/ ")
|
||
sb.WriteString(fmt.Sprintf("sdkversion/%s ", sdkVersion))
|
||
sb.WriteString(fmt.Sprintf("datetime/%d ", time.Now().UnixMilli()))
|
||
sb.WriteString(fmt.Sprintf("usrno/%s ", userID))
|
||
sb.WriteString(fmt.Sprintf("appname/android-%s ", appName))
|
||
sb.WriteString(fmt.Sprintf("session_origin/ "))
|
||
sb.WriteString(fmt.Sprintf("grant_type/ "))
|
||
sb.WriteString(fmt.Sprintf("appid/ "))
|
||
sb.WriteString(fmt.Sprintf("clientip/ "))
|
||
sb.WriteString(fmt.Sprintf("devicename/Xiaomi_M2004j7ac "))
|
||
sb.WriteString(fmt.Sprintf("osversion/13 "))
|
||
sb.WriteString(fmt.Sprintf("platformversion/10 "))
|
||
sb.WriteString(fmt.Sprintf("accessmode/ "))
|
||
sb.WriteString(fmt.Sprintf("devicemodel/M2004J7AC "))
|
||
|
||
return sb.String()
|
||
}
|
||
|
||
func (c *Common) SetDeviceID(deviceID string) {
|
||
c.DeviceID = deviceID
|
||
}
|
||
|
||
func (c *Common) SetUserID(userID string) {
|
||
c.UserID = userID
|
||
}
|
||
|
||
func (c *Common) SetUserAgent(userAgent string) {
|
||
c.UserAgent = userAgent
|
||
}
|
||
|
||
func (c *Common) SetCaptchaToken(captchaToken string) {
|
||
c.CaptchaToken = captchaToken
|
||
}
|
||
func (c *Common) GetCaptchaToken() string {
|
||
return c.CaptchaToken
|
||
}
|
||
|
||
func (c *Common) GetUserAgent() string {
|
||
return c.UserAgent
|
||
}
|
||
|
||
func (c *Common) GetDeviceID() string {
|
||
return c.DeviceID
|
||
}
|
||
|
||
func (c *Common) GetUserID() string {
|
||
return c.UserID
|
||
}
|
||
|
||
// RefreshCaptchaTokenAtLogin 刷新验证码token(登录后)
|
||
func (d *PikPak) RefreshCaptchaTokenAtLogin(action, userID string) error {
|
||
metas := map[string]string{
|
||
"client_version": d.ClientVersion,
|
||
"package_name": d.PackageName,
|
||
"user_id": userID,
|
||
}
|
||
metas["timestamp"], metas["captcha_sign"] = d.Common.GetCaptchaSign()
|
||
return d.refreshCaptchaToken(action, metas)
|
||
}
|
||
|
||
// RefreshCaptchaTokenInLogin 刷新验证码token(登录时)
|
||
func (d *PikPak) RefreshCaptchaTokenInLogin(action, username string) error {
|
||
metas := make(map[string]string)
|
||
if ok, _ := regexp.MatchString(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`, username); ok {
|
||
metas["email"] = username
|
||
} else if len(username) >= 11 && len(username) <= 18 {
|
||
metas["phone_number"] = username
|
||
} else {
|
||
metas["username"] = username
|
||
}
|
||
return d.refreshCaptchaToken(action, metas)
|
||
}
|
||
|
||
// GetCaptchaSign 获取验证码签名
|
||
func (c *Common) GetCaptchaSign() (timestamp, sign string) {
|
||
timestamp = fmt.Sprint(time.Now().UnixMilli())
|
||
str := fmt.Sprint(c.ClientID, c.ClientVersion, c.PackageName, c.DeviceID, timestamp)
|
||
for _, algorithm := range c.Algorithms {
|
||
str = utils.GetMD5EncodeStr(str + algorithm)
|
||
}
|
||
sign = "1." + str
|
||
return
|
||
}
|
||
|
||
// refreshCaptchaToken 刷新CaptchaToken
|
||
func (d *PikPak) refreshCaptchaToken(action string, metas map[string]string) error {
|
||
param := CaptchaTokenRequest{
|
||
Action: action,
|
||
CaptchaToken: d.GetCaptchaToken(),
|
||
ClientID: d.ClientID,
|
||
DeviceID: d.GetDeviceID(),
|
||
Meta: metas,
|
||
RedirectUri: "xlaccsdk01://xbase.cloud/callback?state=harbor",
|
||
}
|
||
var e ErrResp
|
||
var resp CaptchaTokenResponse
|
||
_, err := d.request("https://user.mypikpak.net/v1/shield/captcha/init", http.MethodPost, func(req *resty.Request) {
|
||
req.SetError(&e).SetBody(param).SetQueryParam("client_id", d.ClientID)
|
||
}, &resp)
|
||
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if e.IsError() {
|
||
return errors.New(e.Error())
|
||
}
|
||
|
||
if resp.Url != "" {
|
||
return fmt.Errorf(`need verify: <a target="_blank" href="%s">Click Here</a>`, resp.Url)
|
||
}
|
||
|
||
if d.Common.RefreshCTokenCk != nil {
|
||
d.Common.RefreshCTokenCk(resp.CaptchaToken)
|
||
}
|
||
d.Common.SetCaptchaToken(resp.CaptchaToken)
|
||
return nil
|
||
}
|
||
|
||
func (d *PikPak) UploadByOSS(params *S3Params, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||
ossClient, err := oss.New(params.Endpoint, params.AccessKeyID, params.AccessKeySecret)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
bucket, err := ossClient.Bucket(params.Bucket)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
err = bucket.PutObject(params.Key, stream, OssOption(params)...)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (d *PikPak) UploadByMultipart(params *S3Params, fileSize int64, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||
var (
|
||
chunks []oss.FileChunk
|
||
parts []oss.UploadPart
|
||
imur oss.InitiateMultipartUploadResult
|
||
ossClient *oss.Client
|
||
bucket *oss.Bucket
|
||
err error
|
||
)
|
||
|
||
tmpF, err := stream.CacheFullInTempFile()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if ossClient, err = oss.New(params.Endpoint, params.AccessKeyID, params.AccessKeySecret); err != nil {
|
||
return err
|
||
}
|
||
|
||
if bucket, err = ossClient.Bucket(params.Bucket); err != nil {
|
||
return err
|
||
}
|
||
|
||
ticker := time.NewTicker(time.Hour * 12)
|
||
defer ticker.Stop()
|
||
// 设置超时
|
||
timeout := time.NewTimer(time.Hour * 24)
|
||
|
||
if chunks, err = SplitFile(fileSize); err != nil {
|
||
return err
|
||
}
|
||
|
||
if imur, err = bucket.InitiateMultipartUpload(params.Key,
|
||
oss.SetHeader(OssSecurityTokenHeaderName, params.SecurityToken),
|
||
oss.UserAgentHeader(OSSUserAgent),
|
||
); err != nil {
|
||
return err
|
||
}
|
||
|
||
wg := sync.WaitGroup{}
|
||
wg.Add(len(chunks))
|
||
|
||
chunksCh := make(chan oss.FileChunk)
|
||
errCh := make(chan error)
|
||
UploadedPartsCh := make(chan oss.UploadPart)
|
||
quit := make(chan struct{})
|
||
|
||
// producer
|
||
go chunksProducer(chunksCh, chunks)
|
||
go func() {
|
||
wg.Wait()
|
||
quit <- struct{}{}
|
||
}()
|
||
|
||
// consumers
|
||
for i := 0; i < ThreadsNum; i++ {
|
||
go func(threadId int) {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
errCh <- fmt.Errorf("recovered in %v", r)
|
||
}
|
||
}()
|
||
for chunk := range chunksCh {
|
||
var part oss.UploadPart // 出现错误就继续尝试,共尝试3次
|
||
for retry := 0; retry < 3; retry++ {
|
||
select {
|
||
case <-ticker.C:
|
||
errCh <- errors.Wrap(err, "ossToken 过期")
|
||
default:
|
||
}
|
||
|
||
buf := make([]byte, chunk.Size)
|
||
if _, err = tmpF.ReadAt(buf, chunk.Offset); err != nil && !errors.Is(err, io.EOF) {
|
||
continue
|
||
}
|
||
|
||
b := bytes.NewBuffer(buf)
|
||
if part, err = bucket.UploadPart(imur, b, chunk.Size, chunk.Number, OssOption(params)...); err == nil {
|
||
break
|
||
}
|
||
}
|
||
if err != nil {
|
||
errCh <- errors.Wrap(err, fmt.Sprintf("上传 %s 的第%d个分片时出现错误:%v", stream.GetName(), chunk.Number, err))
|
||
}
|
||
UploadedPartsCh <- part
|
||
}
|
||
}(i)
|
||
}
|
||
|
||
go func() {
|
||
for part := range UploadedPartsCh {
|
||
parts = append(parts, part)
|
||
wg.Done()
|
||
}
|
||
}()
|
||
LOOP:
|
||
for {
|
||
select {
|
||
case <-ticker.C:
|
||
// ossToken 过期
|
||
return err
|
||
case <-quit:
|
||
break LOOP
|
||
case <-errCh:
|
||
return err
|
||
case <-timeout.C:
|
||
return fmt.Errorf("time out")
|
||
}
|
||
}
|
||
|
||
// EOF错误是xml的Unmarshal导致的,响应其实是json格式,所以实际上上传是成功的
|
||
if _, err = bucket.CompleteMultipartUpload(imur, parts, OssOption(params)...); err != nil && !errors.Is(err, io.EOF) {
|
||
// 当文件名含有 &< 这两个字符之一时响应的xml解析会出现错误,实际上上传是成功的
|
||
if filename := filepath.Base(stream.GetName()); !strings.ContainsAny(filename, "&<") {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func chunksProducer(ch chan oss.FileChunk, chunks []oss.FileChunk) {
|
||
for _, chunk := range chunks {
|
||
ch <- chunk
|
||
}
|
||
}
|
||
|
||
func SplitFile(fileSize int64) (chunks []oss.FileChunk, err error) {
|
||
for i := int64(1); i < 10; i++ {
|
||
if fileSize < i*utils.GB { // 文件大小小于iGB时分为i*100片
|
||
if chunks, err = SplitFileByPartNum(fileSize, int(i*100)); err != nil {
|
||
return
|
||
}
|
||
break
|
||
}
|
||
}
|
||
if fileSize > 9*utils.GB { // 文件大小大于9GB时分为1000片
|
||
if chunks, err = SplitFileByPartNum(fileSize, 1000); err != nil {
|
||
return
|
||
}
|
||
}
|
||
// 单个分片大小不能小于1MB
|
||
if chunks[0].Size < 1*utils.MB {
|
||
if chunks, err = SplitFileByPartSize(fileSize, 1*utils.MB); err != nil {
|
||
return
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
// SplitFileByPartNum splits big file into parts by the num of parts.
|
||
// Split the file with specified parts count, returns the split result when error is nil.
|
||
func SplitFileByPartNum(fileSize int64, chunkNum int) ([]oss.FileChunk, error) {
|
||
if chunkNum <= 0 || chunkNum > 10000 {
|
||
return nil, errors.New("chunkNum invalid")
|
||
}
|
||
|
||
if int64(chunkNum) > fileSize {
|
||
return nil, errors.New("oss: chunkNum invalid")
|
||
}
|
||
|
||
var chunks []oss.FileChunk
|
||
chunk := oss.FileChunk{}
|
||
chunkN := (int64)(chunkNum)
|
||
for i := int64(0); i < chunkN; i++ {
|
||
chunk.Number = int(i + 1)
|
||
chunk.Offset = i * (fileSize / chunkN)
|
||
if i == chunkN-1 {
|
||
chunk.Size = fileSize/chunkN + fileSize%chunkN
|
||
} else {
|
||
chunk.Size = fileSize / chunkN
|
||
}
|
||
chunks = append(chunks, chunk)
|
||
}
|
||
|
||
return chunks, nil
|
||
}
|
||
|
||
// SplitFileByPartSize splits big file into parts by the size of parts.
|
||
// Splits the file by the part size. Returns the FileChunk when error is nil.
|
||
func SplitFileByPartSize(fileSize int64, chunkSize int64) ([]oss.FileChunk, error) {
|
||
if chunkSize <= 0 {
|
||
return nil, errors.New("chunkSize invalid")
|
||
}
|
||
|
||
chunkN := fileSize / chunkSize
|
||
if chunkN >= 10000 {
|
||
return nil, errors.New("Too many parts, please increase part size")
|
||
}
|
||
|
||
var chunks []oss.FileChunk
|
||
chunk := oss.FileChunk{}
|
||
for i := int64(0); i < chunkN; i++ {
|
||
chunk.Number = int(i + 1)
|
||
chunk.Offset = i * chunkSize
|
||
chunk.Size = chunkSize
|
||
chunks = append(chunks, chunk)
|
||
}
|
||
|
||
if fileSize%chunkSize > 0 {
|
||
chunk.Number = len(chunks) + 1
|
||
chunk.Offset = int64(len(chunks)) * chunkSize
|
||
chunk.Size = fileSize % chunkSize
|
||
chunks = append(chunks, chunk)
|
||
}
|
||
|
||
return chunks, nil
|
||
}
|
||
|
||
// OssOption get options
|
||
func OssOption(params *S3Params) []oss.Option {
|
||
options := []oss.Option{
|
||
oss.SetHeader(OssSecurityTokenHeaderName, params.SecurityToken),
|
||
oss.UserAgentHeader(OSSUserAgent),
|
||
}
|
||
return options
|
||
}
|