github.com/bhojpur/cache@v0.0.4/pkg/file/cache.go (about)

     1  package file
     2  
     3  // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved.
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  import (
    24  	"encoding/binary"
    25  	"errors"
    26  	"os/user"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	memcache "github.com/bhojpur/cache/pkg/memory"
    32  	"github.com/mattn/go-nulltype"
    33  )
    34  
    35  var (
    36  	NullString = nulltype.NullString{}
    37  )
    38  
    39  type KV struct {
    40  	Key string
    41  	Val string
    42  	TTL time.Duration
    43  }
    44  
    45  type FileCache interface {
    46  	GetBytes(key string) ([]byte, error)
    47  	Get(key string) (nulltype.NullString, error)
    48  	SetBytes(key string, val []byte, ttl time.Duration) error
    49  	Set(key, val string, ttl time.Duration) error
    50  	TTL(key string) (time.Duration, error)
    51  	Expire(key string, ttl time.Duration) error
    52  	Del(key string) error
    53  	Range() ([]*KV, error)
    54  }
    55  
    56  func New(filepath string) FileCache {
    57  	if strings.HasPrefix(filepath, "~") {
    58  		u, err := user.Current()
    59  		if err != nil {
    60  			panic(err)
    61  		}
    62  		filepath = u.HomeDir + filepath[1:]
    63  	}
    64  	return &CacheImpl{
    65  		filepath: filepath,
    66  		bucket:   []byte("filer"),
    67  	}
    68  }
    69  
    70  type CacheImpl struct {
    71  	filepath string
    72  	bucket   []byte
    73  	bOnce    sync.Once
    74  	conn     *memcache.DB
    75  }
    76  
    77  func (r *CacheImpl) GetBytes(key string) ([]byte, error) {
    78  	ttl, result, err := r.getWithExpire(key)
    79  	if err != nil {
    80  		return nil, err
    81  	} else if ttl < 0 {
    82  		return nil, nil
    83  	}
    84  	return result, nil
    85  }
    86  
    87  func (r *CacheImpl) Get(key string) (nulltype.NullString, error) {
    88  	ttl, result, err := r.getWithExpire(key)
    89  	//fmt.Println(ttl, result, err)
    90  	if err != nil {
    91  		return NullString, err
    92  	} else if ttl < 0 {
    93  		return nulltype.NullString{}, nil
    94  	}
    95  	return nulltype.NullStringOf(string(result)), nil
    96  }
    97  
    98  func (r *CacheImpl) SetBytes(key string, val []byte, ttl time.Duration) error {
    99  	if err := r.newConn(); err != nil {
   100  		return err
   101  	}
   102  
   103  	return r.conn.Update(func(tx *memcache.Tx) error {
   104  		b, err := tx.CreateBucketIfNotExists(r.bucket)
   105  		if err != nil {
   106  			return err
   107  		}
   108  
   109  		buf := make([]byte, 8+len(val))
   110  		binary.PutVarint(buf[:8], toMillisecond(ttl))
   111  		copy(buf[8:], val)
   112  
   113  		//fmt.Println(key, buf[:8], buf[8:])
   114  		return b.Put([]byte(key), buf)
   115  	})
   116  }
   117  
   118  func (r *CacheImpl) Set(key, val string, ttl time.Duration) error {
   119  	if err := r.newConn(); err != nil {
   120  		return err
   121  	}
   122  
   123  	return r.conn.Update(func(tx *memcache.Tx) error {
   124  		b, err := tx.CreateBucketIfNotExists(r.bucket)
   125  		if err != nil {
   126  			return err
   127  		}
   128  
   129  		buf := make([]byte, 8+len(val))
   130  		binary.PutVarint(buf[:8], toMillisecond(ttl))
   131  		copy(buf[8:], val)
   132  
   133  		//fmt.Println(key, buf[:8], buf[8:])
   134  		return b.Put([]byte(key), buf)
   135  	})
   136  }
   137  
   138  func (r *CacheImpl) TTL(key string) (time.Duration, error) {
   139  	ttl, _, err := r.getWithExpire(key)
   140  	if err != nil {
   141  		return -1, err
   142  	} else if ttl < -1 {
   143  		return -1, nil
   144  	}
   145  	return time.Duration(ttl) * time.Millisecond, nil
   146  }
   147  
   148  func (r *CacheImpl) Expire(key string, ttl time.Duration) error {
   149  	_, result, err := r.getWithExpire(key)
   150  	if err != nil {
   151  		return err
   152  	} else if ttl < -1 {
   153  		return ErrKeyExpired
   154  	}
   155  	return r.Set(key, string(result), ttl)
   156  }
   157  
   158  func (r *CacheImpl) Del(key string) error {
   159  	if err := r.newConn(); err != nil {
   160  		return err
   161  	}
   162  
   163  	return r.conn.Update(func(tx *memcache.Tx) error {
   164  		b, err := tx.CreateBucketIfNotExists(r.bucket)
   165  		if err != nil {
   166  			return err
   167  		}
   168  		return b.Delete([]byte(key))
   169  	})
   170  }
   171  
   172  func (r *CacheImpl) Range() ([]*KV, error) {
   173  	if err := r.newConn(); err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	var kvs []*KV
   178  	if err := r.conn.View(func(tx *memcache.Tx) error {
   179  		b := tx.Bucket(r.bucket)
   180  		if b == nil {
   181  			return nil
   182  		}
   183  		return b.ForEach(func(k, v []byte) error {
   184  			expiredAt, err := binaryInt(v[:8])
   185  			if err != nil {
   186  				return err
   187  			}
   188  			ttl := expiredAt - int(time.Now().UnixNano()/int64(1000000))
   189  			if ttl < 0 {
   190  				// TODO: 删除
   191  				return nil
   192  			}
   193  
   194  			kvs = append(kvs, &KV{
   195  				Key: string(k),
   196  				Val: string(v[8:]),
   197  				TTL: time.Duration(ttl) * time.Millisecond,
   198  			})
   199  			return nil
   200  		})
   201  	}); err != nil {
   202  		return nil, err
   203  	}
   204  	return kvs, nil
   205  }
   206  
   207  func (r *CacheImpl) newConn() error {
   208  	if r.conn == nil {
   209  		db, err := memcache.Open(r.filepath, 0600, nil)
   210  		if err != nil {
   211  			return err
   212  		}
   213  		r.conn = db
   214  	}
   215  	return nil
   216  }
   217  
   218  func (r *CacheImpl) getOriginData(key string) ([]byte, error) {
   219  	if err := r.newConn(); err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	var result []byte
   224  	if err := r.conn.View(func(tx *memcache.Tx) error {
   225  		b := tx.Bucket(r.bucket)
   226  		if b == nil {
   227  			return nil
   228  		}
   229  
   230  		result = b.Get([]byte(key))
   231  		return nil
   232  	}); err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	return result, nil
   237  }
   238  
   239  func (r *CacheImpl) getWithExpire(key string) (int, []byte, error) {
   240  	result, err := r.getOriginData(key)
   241  	//fmt.Println(1, result, err)
   242  	if err != nil {
   243  		return -1, nil, nil
   244  	} else if result == nil {
   245  		return -1, nil, nil
   246  	}
   247  	expiredAt, err := binaryInt(result[:8])
   248  	if err != nil {
   249  		return -1, nil, err
   250  	}
   251  	ttl := expiredAt - int(time.Now().UnixNano()/int64(1000000))
   252  	if ttl < 0 {
   253  		// 过期了
   254  		// TODO: 删除
   255  		return -1, nil, err
   256  	}
   257  
   258  	return ttl, result[8:], nil
   259  }
   260  
   261  func toMillisecond(ttl time.Duration) int64 {
   262  	return int64(time.Now().Add(ttl).UnixNano() / int64(1000000))
   263  }
   264  
   265  func binaryInt(buf []byte) (int, error) {
   266  	x, n := binary.Varint(buf)
   267  	if n == 0 {
   268  		return 0, errors.New("buf too small")
   269  	} else if n < 0 {
   270  		return 0, errors.New("value larger than 64 bits (overflow) and -n is the number of bytes read")
   271  	}
   272  
   273  	return int(x), nil
   274  }