github.com/keysonZZZ/kmg@v0.0.0-20151121023212-05317bfd7d39/third/kmgQiniu/Context.go (about)

     1  package kmgQiniu
     2  
     3  import (
     4  	"github.com/qiniu/api/conf"
     5  	qiniuIo "github.com/qiniu/api/io"
     6  	"github.com/qiniu/api/rs"
     7  	"github.com/qiniu/api/rsf"
     8  
     9  	"bytes"
    10  	"fmt"
    11  	"hash/crc32"
    12  	"io"
    13  	"io/ioutil"
    14  	"net/http"
    15  	"strings"
    16  	"time"
    17  )
    18  
    19  type Context struct {
    20  	client    rs.Client
    21  	rsfClient rsf.Client
    22  	bucket    string //bucket名
    23  	domain    string //下载域名
    24  	isPrivate bool   //是否是私有bucket
    25  }
    26  
    27  type Bucket struct {
    28  	Ak string
    29  	Sk string
    30  
    31  	Name      string //空间名
    32  	Domain    string //下载使用的域名
    33  	IsPrivate bool   // 是否是私有Api
    34  }
    35  
    36  var currentContext *Context
    37  
    38  //注意: 由于实现的问题,全局只能使用一个Context,
    39  // TODO 解决全局只能使用一个Context的问题
    40  func NewContext(bucket Bucket) *Context {
    41  	conf.ACCESS_KEY = bucket.Ak
    42  	conf.SECRET_KEY = bucket.Sk
    43  	currentContext = &Context{
    44  		client:    rs.New(nil),
    45  		rsfClient: rsf.New(nil),
    46  		bucket:    bucket.Name,
    47  		domain:    bucket.Domain,
    48  		isPrivate: bucket.IsPrivate,
    49  	}
    50  	return currentContext
    51  }
    52  
    53  //可以下载文件或目录 remoteRoot 开头带 / 或不带 / 效果一致
    54  func (ctx *Context) DownloadToFile(remoteRoot string, localRoot string) (err error) {
    55  	ctx.singleContextCheck()
    56  	remoteRoot = strings.TrimPrefix(remoteRoot, "/")
    57  	return DownloadDir(ctx, remoteRoot, localRoot)
    58  }
    59  
    60  func (ctx *Context) MustDownloadToFile(remoteRoot string, localRoot string) {
    61  	ctx.singleContextCheck()
    62  	remoteRoot = strings.TrimPrefix(remoteRoot, "/")
    63  	err := DownloadDir(ctx, remoteRoot, localRoot)
    64  	if err != nil {
    65  		panic(err)
    66  	}
    67  	return
    68  }
    69  
    70  // 下载一个文件, 开头带 / 或不带 / 效果一致
    71  func (ctx *Context) DownloadOneToFile(remoteRoot string, localRoot string) (err error) {
    72  	ctx.singleContextCheck()
    73  	remoteRoot = strings.TrimPrefix(remoteRoot, "/")
    74  	err = DownloadFile(ctx, remoteRoot, localRoot)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	return nil
    79  }
    80  
    81  //下载到一个Writer里面
    82  func (ctx *Context) DownloadToWriter(remotePath string, w io.Writer) (err error) {
    83  	ctx.singleContextCheck()
    84  	remotePath = strings.TrimPrefix(remotePath, "/")
    85  	var downloadUrl string
    86  	if ctx.isPrivate {
    87  		baseUrl := rs.MakeBaseUrl(ctx.domain, remotePath)
    88  		policy := rs.GetPolicy{}
    89  		downloadUrl = policy.MakeRequest(baseUrl, nil)
    90  	} else {
    91  		downloadUrl = rs.MakeBaseUrl(ctx.domain, remotePath)
    92  	}
    93  	resp, err := http.Get(downloadUrl)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	if resp.StatusCode == 404 {
    98  		return ErrNoFile
    99  	}
   100  	if resp.StatusCode != 200 {
   101  		return fmt.Errorf("resp.StatusCode[%d]!=200", resp.StatusCode)
   102  	}
   103  	defer resp.Body.Close()
   104  	_, err = io.Copy(w, resp.Body)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	return
   109  }
   110  
   111  func (ctx *Context) MustDownloadToBytes(remotePath string) (b []byte) {
   112  	ctx.singleContextCheck()
   113  	buf := &bytes.Buffer{}
   114  	err := ctx.DownloadToWriter(remotePath, buf)
   115  	if err != nil {
   116  		panic(err)
   117  	}
   118  	return buf.Bytes()
   119  }
   120  
   121  func (ctx *Context) DownloadToBytes(remotePath string) (b []byte, err error) {
   122  	ctx.singleContextCheck()
   123  	buf := &bytes.Buffer{}
   124  	err = ctx.DownloadToWriter(remotePath, buf)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	return buf.Bytes(), nil
   129  }
   130  
   131  //可以上传文件或目录 remoteRoot 开头带 / 或不带 / 效果一致
   132  func (ctx *Context) UploadFromFile(localRoot string, remoteRoot string) (err error) {
   133  	ctx.singleContextCheck()
   134  	remoteRoot = strings.TrimPrefix(remoteRoot, "/")
   135  	return UploadDirMulitThread(ctx, localRoot, remoteRoot)
   136  }
   137  
   138  func (ctx *Context) MustUploadFromFile(localRoot string, remoteRoot string) {
   139  	ctx.singleContextCheck()
   140  	remoteRoot = strings.TrimPrefix(remoteRoot, "/")
   141  	err := UploadDirMulitThread(ctx, localRoot, remoteRoot)
   142  	if err != nil {
   143  		panic(err)
   144  	}
   145  	return
   146  }
   147  
   148  //上传字节 remotePath 开头带 / 或不带 / 效果完全不一样. 正常情况应该是不带 /的
   149  func (ctx *Context) UploadFromBytes(remotePath string, b []byte) (err error) {
   150  	ctx.singleContextCheck()
   151  	remotePath = strings.TrimPrefix(remotePath, "/")
   152  	h := crc32.NewIEEE()
   153  	h.Write(b)
   154  	crc := h.Sum32()
   155  	var ret qiniuIo.PutRet
   156  	var extra = &qiniuIo.PutExtra{
   157  		Crc32:    crc,
   158  		CheckCrc: 2,
   159  	}
   160  	putPolicy := rs.PutPolicy{
   161  		Scope: ctx.bucket + ":" + remotePath,
   162  	}
   163  	uptoken := putPolicy.Token(nil)
   164  	r := bytes.NewReader(b)
   165  	err = qiniuIo.Put2(nil, &ret, uptoken, remotePath, r, int64(len(b)), extra)
   166  	if err != nil {
   167  		return
   168  	}
   169  	expectHash := ComputeHashFromBytes(b)
   170  	if ret.Hash != expectHash {
   171  		return fmt.Errorf("[UploadFileWithHash][remotePath:%s] ret.Hash:[%s]!=expectHash[%s] ", remotePath, ret.Hash, expectHash)
   172  	}
   173  	return
   174  }
   175  
   176  //上传字节 remotePath 开头带 / 或不带 / 效果完全不一样. 正常情况应该是不带 /的
   177  // 此处没有实现流式接口,这个接口的效果和 UploadFromBytes 没有什么差别,(依然会爆内存)
   178  // 分片上传 功能似乎可以解决此类问题,可惜太过复杂了.
   179  func (ctx *Context) UploadFromReader(remotePath string, reader io.Reader) (err error) {
   180  	buf, err := ioutil.ReadAll(reader)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	return ctx.UploadFromBytes(remotePath, buf)
   185  }
   186  
   187  func (ctx *Context) MustUploadFromBytes(remotePath string, context []byte) {
   188  	ctx.singleContextCheck()
   189  	err := ctx.UploadFromBytes(remotePath, context)
   190  	if err != nil {
   191  		panic(err)
   192  	}
   193  	return
   194  }
   195  
   196  //prefix 开头带 / 或不带 / 效果一致
   197  func (ctx *Context) RemovePrefix(prefix string) (err error) {
   198  	ctx.singleContextCheck()
   199  	prefix = strings.TrimPrefix(prefix, "/")
   200  	return RemovePrefix(ctx, prefix)
   201  }
   202  
   203  // 目录开头带 / 或不带 / 效果一致
   204  func (ctx *Context) MustRemoveBatch(PathList []string) {
   205  	ctx.singleContextCheck()
   206  	if len(PathList) == 0 {
   207  		return
   208  	}
   209  	//这个好像也有1000个文件的限制.
   210  	deleteItemList := make([]rs.EntryPath, 0, len(PathList))
   211  	length := len(PathList)
   212  	for i := 0; i < length; i += 1000 {
   213  		end := i + 1000
   214  		if end > length {
   215  			end = length
   216  		}
   217  		deleteItemList = deleteItemList[0:0]
   218  		for j := i; j < end; j++ {
   219  			path := strings.TrimPrefix(PathList[j], "/")
   220  			deleteItemList = append(deleteItemList, rs.EntryPath{
   221  				Key:    path,
   222  				Bucket: ctx.bucket,
   223  			})
   224  		}
   225  		_, err := ctx.client.BatchDelete(nil, deleteItemList)
   226  		if err != nil {
   227  			panic(err)
   228  		}
   229  	}
   230  }
   231  
   232  // 返回 scheme和domain ,结尾没有 /
   233  // 例如: http://xxx.com
   234  func (ctx *Context) GetSchemeAndDomain() string {
   235  	return "http://" + ctx.domain
   236  }
   237  
   238  func (ctx *Context) GetName() string {
   239  	return ctx.bucket
   240  }
   241  
   242  type FileInfo struct {
   243  	Path    string //
   244  	Hash    string
   245  	Size    int64
   246  	ModTime time.Time
   247  	//还有几个字段暂时用不着.
   248  }
   249  
   250  func (fi FileInfo) IsExist() bool {
   251  	return fi.Hash != ""
   252  }
   253  
   254  func (ctx *Context) ListPrefix(prefix string) (output []FileInfo, err error) {
   255  	ctx.singleContextCheck()
   256  	prefix = strings.TrimPrefix(prefix, "/")
   257  	entries, err := ListPrefix(ctx, prefix)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	output = make([]FileInfo, len(entries))
   262  	for i := range entries {
   263  		output[i].Path = entries[i].Key
   264  		output[i].Hash = entries[i].Hash
   265  		output[i].Size = entries[i].Fsize
   266  		output[i].ModTime = time.Unix(entries[i].PutTime/1e7, entries[i].PutTime%1e7*100)
   267  	}
   268  	return output, nil
   269  }
   270  
   271  // 返回的path前面不带 /
   272  func (ctx *Context) MustListPrefix(prefix string) (output []FileInfo) {
   273  	output, err := ctx.ListPrefix(prefix)
   274  	if err != nil {
   275  		panic(err)
   276  	}
   277  	return output
   278  }
   279  
   280  // 批量获取文件信息
   281  // PathList 是远程路径
   282  // 路径里面开头带 / 和不带 / 效果一致.
   283  // FileInfo 里面的 Hash是空表示没有找到文件.
   284  func (ctx *Context) BatchStat(PathList []string) (output []FileInfo, err error) {
   285  	// 这个好像也有1000个的限制
   286  	// TODO 并发Stat
   287  	ctx.singleContextCheck()
   288  	if len(PathList) == 0 {
   289  		return nil, nil
   290  	}
   291  	output = make([]FileInfo, 0, len(PathList))
   292  	itemList := make([]rs.EntryPath, 0, len(PathList))
   293  	length := len(PathList)
   294  	for i := 0; i < length; i += 1000 {
   295  		end := i + 1000
   296  		if end > length {
   297  			end = length
   298  		}
   299  		itemList = itemList[0:0]
   300  		for j := i; j < end; j++ {
   301  			path := strings.TrimPrefix(PathList[j], "/")
   302  			itemList = append(itemList, rs.EntryPath{
   303  				Key:    path,
   304  				Bucket: ctx.bucket,
   305  			})
   306  		}
   307  		batchRet, err := ctx.client.BatchStat(nil, itemList)
   308  		//此处返回的错误很奇怪,有大量文件不存在信息,应该是正常情况,此处最简单的解决方案就是假设没有错误
   309  		if len(batchRet) != len(itemList) {
   310  			// 这种是真出现错误了.
   311  			return nil, fmt.Errorf("[BatchStat] len(batchRet)[%d]!=len(entryPathList)[%d] err[%s]",
   312  				len(batchRet), len(itemList), err)
   313  		}
   314  		for i := range batchRet {
   315  			ret := batchRet[i]
   316  			if ret.Error != "" {
   317  				if ret.Error != "no such file or directory" {
   318  					output = append(output, FileInfo{
   319  						Path: itemList[i].Key,
   320  					})
   321  					continue
   322  				} else {
   323  					return nil, fmt.Errorf("[BatchStat] unexpect err [%s] code [%d]", ret.Error, ret.Code)
   324  				}
   325  			}
   326  			output = append(output, FileInfo{
   327  				Path:    itemList[i].Key,
   328  				Hash:    batchRet[i].Data.Hash,
   329  				Size:    batchRet[i].Data.Fsize,
   330  				ModTime: time.Unix(batchRet[i].Data.PutTime/1e7, batchRet[i].Data.PutTime%1e7*100),
   331  			})
   332  		}
   333  	}
   334  	return output, nil
   335  }
   336  
   337  func (ctx *Context) singleContextCheck() {
   338  	if ctx != currentContext {
   339  		panic("同时只能有一个Context存在.")
   340  	}
   341  }