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 }