hook-reader.go (2661B)
1 /* 2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage 3 * Copyright 2015-2017 MinIO, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package minio 19 20 import ( 21 "fmt" 22 "io" 23 "sync" 24 ) 25 26 // hookReader hooks additional reader in the source stream. It is 27 // useful for making progress bars. Second reader is appropriately 28 // notified about the exact number of bytes read from the primary 29 // source on each Read operation. 30 type hookReader struct { 31 mu sync.RWMutex 32 source io.Reader 33 hook io.Reader 34 } 35 36 // Seek implements io.Seeker. Seeks source first, and if necessary 37 // seeks hook if Seek method is appropriately found. 38 func (hr *hookReader) Seek(offset int64, whence int) (n int64, err error) { 39 hr.mu.Lock() 40 defer hr.mu.Unlock() 41 42 // Verify for source has embedded Seeker, use it. 43 sourceSeeker, ok := hr.source.(io.Seeker) 44 if ok { 45 n, err = sourceSeeker.Seek(offset, whence) 46 if err != nil { 47 return 0, err 48 } 49 } 50 51 if hr.hook != nil { 52 // Verify if hook has embedded Seeker, use it. 53 hookSeeker, ok := hr.hook.(io.Seeker) 54 if ok { 55 var m int64 56 m, err = hookSeeker.Seek(offset, whence) 57 if err != nil { 58 return 0, err 59 } 60 if n != m { 61 return 0, fmt.Errorf("hook seeker seeked %d bytes, expected source %d bytes", m, n) 62 } 63 } 64 } 65 66 return n, nil 67 } 68 69 // Read implements io.Reader. Always reads from the source, the return 70 // value 'n' number of bytes are reported through the hook. Returns 71 // error for all non io.EOF conditions. 72 func (hr *hookReader) Read(b []byte) (n int, err error) { 73 hr.mu.RLock() 74 defer hr.mu.RUnlock() 75 76 n, err = hr.source.Read(b) 77 if err != nil && err != io.EOF { 78 return n, err 79 } 80 if hr.hook != nil { 81 // Progress the hook with the total read bytes from the source. 82 if _, herr := hr.hook.Read(b[:n]); herr != nil { 83 if herr != io.EOF { 84 return n, herr 85 } 86 } 87 } 88 return n, err 89 } 90 91 // newHook returns a io.ReadSeeker which implements hookReader that 92 // reports the data read from the source to the hook. 93 func newHook(source, hook io.Reader) io.Reader { 94 if hook == nil { 95 return &hookReader{source: source} 96 } 97 return &hookReader{ 98 source: source, 99 hook: hook, 100 } 101 }