github.com/iDigitalFlame/xmt@v0.5.4/data/chunk_base.go (about)

     1  // Copyright (C) 2020 - 2023 iDigitalFlame
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU General Public License as published by
     5  // the Free Software Foundation, either version 3 of the License, or
     6  // any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU General Public License
    14  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    15  //
    16  
    17  package data
    18  
    19  import (
    20  	"io"
    21  	"net"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/iDigitalFlame/xmt/util/bugtrack"
    26  	"github.com/iDigitalFlame/xmt/util/xerr"
    27  )
    28  
    29  const (
    30  	max     = int(^uint(0) >> 1)
    31  	bufSize = 2 << 13
    32  )
    33  
    34  var bufs = sync.Pool{
    35  	New: func() interface{} {
    36  		var b [bufSize]byte
    37  		return &b
    38  	},
    39  }
    40  
    41  // Flush allows Chunk to support the io.Flusher interface.
    42  func (Chunk) Flush() error {
    43  	return nil
    44  }
    45  
    46  // Close allows Chunk to support the io.Closer interface.
    47  func (Chunk) Close() error {
    48  	return nil
    49  }
    50  
    51  // Space returns the amount of bytes available in this Chunk when a Limit is
    52  // set.
    53  //
    54  // This function will return -1 if there is no limit set and returns 0 (zero)
    55  // when a limit is set, but no byte space is available.
    56  func (c *Chunk) Space() int {
    57  	if c.Limit <= 0 {
    58  		return -1
    59  	}
    60  	//if c.Empty() {
    61  	//	return 0
    62  	//}
    63  	if r := c.Limit - c.Size(); r > 0 {
    64  		return r
    65  	}
    66  	return 0
    67  }
    68  
    69  // String returns a string representation of this Chunk's buffer.
    70  func (c *Chunk) String() string {
    71  	if c.Empty() {
    72  		return "<nil>"
    73  	}
    74  	_ = c.buf[c.rpos]
    75  	return string(c.buf[c.rpos:])
    76  }
    77  
    78  // Remaining returns the number of bytes left to be read in this Chunk. This is
    79  // the length 'Size' minus the read cursor.
    80  func (c *Chunk) Remaining() int {
    81  	if c.Empty() {
    82  		return 0
    83  	}
    84  	return c.Size() - c.rpos
    85  }
    86  
    87  // Payload returns a copy of the underlying UNREAD buffer contained in this
    88  // Chunk.
    89  //
    90  // This may be empty depending on the read status of this chunk. To retrieve the
    91  // full buffer, use the 'Seek' function to set the read cursor to zero.
    92  func (c *Chunk) Payload() []byte {
    93  	if c.Empty() || c.rpos > c.Size() {
    94  		return nil
    95  	}
    96  	_ = c.buf[c.Size()-1]
    97  	return c.buf[c.rpos:c.Size()]
    98  }
    99  
   100  // Available returns if a limit will block the writing of n bytes. This function
   101  // can be used to check if there is space to write before committing a write.
   102  func (c *Chunk) Available(n int) bool {
   103  	return c.Limit <= 0 || c.Limit-c.Size() > n
   104  }
   105  
   106  // Read reads the next len(p) bytes from the Chunk or until the Chunk is
   107  // drained. The return value n is the number of bytes read and any errors that
   108  // may have occurred.
   109  func (c *Chunk) Read(b []byte) (int, error) {
   110  	if c.Empty() && c.buf != nil {
   111  		if c.Reset(); len(b) == 0 {
   112  			return 0, nil
   113  		}
   114  		return 0, io.EOF
   115  	}
   116  	n := copy(b, c.buf[c.rpos:])
   117  	c.rpos += n
   118  	return n, nil
   119  }
   120  
   121  // Write appends the contents of b to the buffer, growing the buffer as needed.
   122  //
   123  // If the buffer becomes too large, Write will return 'ErrTooLarge.' If there is
   124  // a limit set, this function will return 'ErrLimit' if the Limit is being hit.
   125  //
   126  // If an 'ErrLimit' is returned, check the returned bytes as 'ErrLimit' is
   127  // returned as a warning that not all bytes have been written before refusing
   128  // writes.
   129  func (c *Chunk) Write(b []byte) (int, error) {
   130  	m, err := c.quickSlice(len(b))
   131  	if err != nil {
   132  		return 0, err
   133  	}
   134  	n := copy(c.buf[m:], b)
   135  	if n < len(b) && c.Limit > 0 && len(c.buf) >= c.Limit {
   136  		return n, ErrLimit
   137  	}
   138  	return n, nil
   139  }
   140  
   141  // MarshalStream writes the unread Chunk data into a binary data representation.
   142  // This function will return an error if any part of the write fails.
   143  func (c *Chunk) MarshalStream(w Writer) error {
   144  	if bugtrack.Enabled {
   145  		if _, ok := w.(*Chunk); ok {
   146  			bugtrack.Track("data.(*Chunk).MarshalStream(): MarshalStream was called from a Chunk to a Chunk!")
   147  		}
   148  	}
   149  	return w.WriteBytes(c.buf[c.rpos:])
   150  }
   151  
   152  // WriteTo writes data to the supplied Writer until there's no more data to
   153  // write or when an error occurs.
   154  //
   155  // The return value is the number of bytes written. Any error encountered
   156  // during the write is also returned.
   157  func (c *Chunk) WriteTo(w io.Writer) (int64, error) {
   158  	if c.Empty() {
   159  		return 0, nil
   160  	}
   161  	var (
   162  		n   int
   163  		err error
   164  	)
   165  	for v, s, e := 0, c.rpos, c.rpos+bufSize; n < c.Size() && err == nil; {
   166  		if e > c.Size() {
   167  			e = c.Size()
   168  		}
   169  		if s == e {
   170  			break
   171  		}
   172  		v, err = w.Write(c.buf[s:e])
   173  		if n += v; err != nil {
   174  			break
   175  		}
   176  		s = e
   177  		e += v
   178  	}
   179  	c.rpos += n
   180  	return int64(n), err
   181  }
   182  
   183  // Seek will attempt to seek to the provided read offset index and whence. This
   184  // function will return the new offset if successful and will return an error
   185  // if the offset and/or whence are invalid.
   186  //
   187  // NOTE: This only affects read operations.
   188  func (c *Chunk) Seek(o int64, w int) (int64, error) {
   189  	switch w {
   190  	case io.SeekStart:
   191  		if o < 0 {
   192  			return 0, ErrInvalidIndex
   193  		}
   194  	case io.SeekCurrent:
   195  		o += int64(c.rpos)
   196  	case io.SeekEnd:
   197  		o += int64(c.Size())
   198  	default:
   199  		return 0, xerr.Sub("invalid whence", 0x27)
   200  	}
   201  	if o < 0 || int(o) > c.Size() {
   202  		return 0, ErrInvalidIndex
   203  	}
   204  	c.rpos = int(o)
   205  	return o, nil
   206  }
   207  
   208  // ReadFrom reads data from the supplied Reader until EOF or error.
   209  //
   210  // The return value is the number of bytes read.
   211  // Any error except 'io.EOF' encountered during the read is also returned.
   212  func (c *Chunk) ReadFrom(r io.Reader) (int64, error) {
   213  	var (
   214  		b         = bufs.Get().(*[bufSize]byte)
   215  		t         int64
   216  		n, w      int
   217  		err, err2 error
   218  	)
   219  	for {
   220  		if c.Limit > 0 {
   221  			x := c.Space()
   222  			if x <= 0 {
   223  				break
   224  			}
   225  			if x > bufSize {
   226  				x = bufSize
   227  			}
   228  			n, err = r.Read((*b)[:x])
   229  		} else {
   230  			n, err = r.Read((*b)[:])
   231  		}
   232  		if n > 0 {
   233  			w, err2 = c.Write((*b)[:n])
   234  			if w < n {
   235  				t += int64(w)
   236  			} else {
   237  				t += int64(n)
   238  			}
   239  			if err2 != nil {
   240  				break
   241  			}
   242  		}
   243  		if bugtrack.Enabled {
   244  			bugtrack.Track("data.(*Chunk).ReadFrom(): n=%d, t=%d, len(b)=%d, err=%s, err2=%s", n, t, len(*b), err, err2)
   245  		}
   246  		if n == 0 || err != nil || err2 != nil || (c.Limit > 0 && n >= c.Limit) {
   247  			if err == io.EOF || err == ErrLimit {
   248  				err = nil
   249  			}
   250  			break
   251  		}
   252  	}
   253  	if bufs.Put(b); bugtrack.Enabled {
   254  		bugtrack.Track("data.(*Chunk).ReadFrom(): return t=%d, err=%s, err2=%s", t, err, err2)
   255  	}
   256  	return t, err
   257  }
   258  
   259  // ReadDeadline reads data from the supplied net.Conn until EOF or error.
   260  //
   261  // The return value is the number of bytes read.
   262  // Any error except 'io.EOF' encountered during the read is also returned.
   263  //
   264  // If the specific duration is greater than zero, the read deadline will be
   265  // applied. Timeout errors will NOT be returned and will instead break a read.
   266  func (c *Chunk) ReadDeadline(r net.Conn, d time.Duration) (int64, error) {
   267  	var (
   268  		b         = bufs.Get().(*[bufSize]byte)
   269  		t         int64
   270  		n, w      int
   271  		err, err2 error
   272  	)
   273  	if bugtrack.Enabled {
   274  		bugtrack.Track("data.(*Chunk).ReadDeadline(): start, d=%s", d.String())
   275  	}
   276  	for {
   277  		if c.Limit > 0 {
   278  			x := c.Space()
   279  			if x <= 0 {
   280  				break
   281  			}
   282  			if x > bufSize {
   283  				x = bufSize
   284  			}
   285  			n, err = r.Read((*b)[:x])
   286  		} else {
   287  			n, err = r.Read((*b)[:])
   288  		}
   289  		if n > 0 {
   290  			w, err2 = c.Write((*b)[:n])
   291  			if w < n {
   292  				t += int64(w)
   293  			} else {
   294  				t += int64(n)
   295  			}
   296  			if err2 != nil {
   297  				break
   298  			}
   299  		}
   300  		if bugtrack.Enabled {
   301  			bugtrack.Track("data.(*Chunk).ReadDeadline(): n=%d, t=%d, len(b)=%d, err=%s, err2=%s", n, t, len(*b), err, err2)
   302  		}
   303  		if n == 0 || err != nil || err2 != nil || (c.Limit > 0 && n >= c.Limit) {
   304  			if e, ok := err.(net.Error); ok && e.Timeout() {
   305  				err = nil
   306  			} else if err == io.EOF || err == ErrLimit {
   307  				err = nil
   308  			}
   309  			break
   310  		}
   311  		if d > 0 {
   312  			r.SetReadDeadline(time.Now().Add(d))
   313  		}
   314  	}
   315  	if bufs.Put(b); bugtrack.Enabled {
   316  		bugtrack.Track("data.(*Chunk).ReadDeadline(): return t=%d, err=%s", t, err)
   317  	}
   318  	return t, err
   319  }