mirror of https://github.com/aldy505/cheapcash.git
feat: initialize
This commit is contained in:
commit
9d74097a50
|
@ -0,0 +1,13 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 80
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = "tabs"
|
||||||
|
tab_width = 4
|
|
@ -0,0 +1,23 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.17.x
|
||||||
|
- name: Go vet
|
||||||
|
run: go vet -v
|
||||||
|
- name: Go test
|
||||||
|
run: go test -v -race -coverprofile=coverage.out -covermode=atomic -failfast
|
||||||
|
- uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: go
|
||||||
|
- uses: github/codeql-action/analyze@v1
|
||||||
|
- uses: codecov/codecov-action@v1
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
### Go Patch ###
|
||||||
|
/vendor/
|
||||||
|
/Godeps/
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Reinaldy Rafli <aldy505@tutanota.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Cheapcash
|
||||||
|
|
||||||
|
[![Go Reference](https://pkg.go.dev/badge/github.com/aldy505/cheapcash.svg)](https://pkg.go.dev/github.com/aldy505/cheapcash) [![Go Report Card](https://goreportcard.com/badge/github.com/aldy505/cheapcash)](https://goreportcard.com/report/github.com/aldy505/cheapcash) ![GitHub](https://img.shields.io/github/license/aldy505/cheapcash) [![CodeFactor](https://www.codefactor.io/repository/github/aldy505/cheapcash/badge)](https://www.codefactor.io/repository/github/aldy505/cheapcash) [![codecov](https://codecov.io/gh/aldy505/cheapcash/branch/master/graph/badge.svg?token=Noeexg5xEJ)](https://codecov.io/gh/aldy505/cheapcash) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/9b78970127c74c1a923533e05f65848d)](https://www.codacy.com/gh/aldy505/cheapcash/dashboard?utm_source=github.com&utm_medium=referral&utm_content=aldy505/cheapcash&utm_campaign=Badge_Grade) [![Test and coverage](https://github.com/aldy505/cheapcash/actions/workflows/ci.yml/badge.svg)](https://github.com/aldy505/cheapcash/actions/workflows/ci.yml)
|
||||||
|
|
||||||
|
SSD is cheap. Why don't we use it for caching?
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/aldy505/cheapcash"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
It has simple API for reading & storing cache.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/aldy505/cheapcash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create a Cheapcash instance.
|
||||||
|
// Of course you can make multiple instance for multiple
|
||||||
|
// root directories.
|
||||||
|
cache := cheapcash.New("/tmp/cheapcash")
|
||||||
|
// or if you are feeling lazy
|
||||||
|
cache = cheapcash.Default()
|
||||||
|
// path defaults to /tmp/cheapcash
|
||||||
|
|
||||||
|
err := cache.Write("users:list", usersList)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := cache.Read("users:list")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(string(val))
|
||||||
|
|
||||||
|
err = cache.Append("users:list", []byte("\nMarcel"))
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package cheapcash
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func (c *Cache) Append(key string, value []byte) error {
|
||||||
|
check, err := c.Exists(c.Path + key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !check {
|
||||||
|
return ErrNotExists
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(sanitizePath(c.Path+key), os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.Write(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package cheapcash_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aldy505/cheapcash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppend(t *testing.T) {
|
||||||
|
rand := strconv.Itoa(rand.Int())
|
||||||
|
c := cheapcash.Default()
|
||||||
|
|
||||||
|
err := c.Write(rand, []byte("Hello"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Append(rand, []byte("World"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := c.Read(rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(r) != "HelloWorld" {
|
||||||
|
t.Errorf("expected %s, got %v", "HelloWorld", string(r))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package cheapcash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cache struct {
|
||||||
|
sync.Mutex
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNotExists = errors.New("key does not exist")
|
||||||
|
var ErrInvalidPath = errors.New("path supplied is invalid")
|
||||||
|
var ErrDiskFull = errors.New("there was no space left on the device")
|
||||||
|
|
||||||
|
func Default() *Cache {
|
||||||
|
return &Cache{
|
||||||
|
Path: "/tmp/cheapcash/",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(path string) *Cache {
|
||||||
|
return &Cache{
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package cheapcash_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aldy505/cheapcash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
removeDirIfExists("/tmp/cheapcash")
|
||||||
|
defer removeDirIfExists("/tmp/cheapcash")
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefault(t *testing.T) {
|
||||||
|
c := cheapcash.Default()
|
||||||
|
if c.Path != "/tmp/cheapcash/" {
|
||||||
|
t.Error("expected path to return /tmp/cheapcash/, got:", c.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
c := cheapcash.New("/somewhere")
|
||||||
|
if c.Path != "/somewhere" {
|
||||||
|
t.Error("expected path to return /somewhere, got:", c.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDirIfExists(path string) {
|
||||||
|
dir, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
if dir.IsDir() {
|
||||||
|
err = os.RemoveAll(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("unable to remove temp directory:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package cheapcash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check whether or not a key exists.
|
||||||
|
// Returns true if the key exists, false otherwise.
|
||||||
|
//
|
||||||
|
// WARNING: You should provide your c.Path value yourself.
|
||||||
|
//
|
||||||
|
// check, err := cache.Exists("something.txt")
|
||||||
|
// // will search in ./something.txt
|
||||||
|
//
|
||||||
|
// check, err = cache.Exists(c.Path + "something.txt")
|
||||||
|
// // will search relative to c.Path value
|
||||||
|
func (c *Cache) Exists(key string) (bool, error) {
|
||||||
|
file, err := os.Open(sanitizePath(key))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDir(path string) error {
|
||||||
|
// Remove / from path
|
||||||
|
path = strings.TrimSuffix(path, "/")
|
||||||
|
|
||||||
|
dir, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
if dir.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create directory with a loop
|
||||||
|
separated := strings.Split(path, "/")
|
||||||
|
|
||||||
|
for i := 0; i < len(separated); i++ {
|
||||||
|
if separated[i] == "" {
|
||||||
|
os.Chdir("/")
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
os.Chdir(separated[i-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Mkdir(separated[i], fs.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrExist) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package cheapcash_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aldy505/cheapcash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExists(t *testing.T) {
|
||||||
|
rand := strconv.Itoa(rand.Int())
|
||||||
|
c := cheapcash.Default()
|
||||||
|
b, err := c.Exists(c.Path + "/" + rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b == true {
|
||||||
|
t.Error("expected false, got true")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Write(rand, []byte("value"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = c.Exists(c.Path + "/" + rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b == false {
|
||||||
|
t.Error("expected true, got false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExists_Conccurency(t *testing.T) {
|
||||||
|
rand := strconv.Itoa(rand.Int())
|
||||||
|
c := cheapcash.Default()
|
||||||
|
|
||||||
|
res := make(chan bool, 5)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
b, err := c.Exists(c.Path + "/" + rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
if b == true {
|
||||||
|
t.Error("expected false, got true")
|
||||||
|
}
|
||||||
|
res <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
b, err := c.Exists(c.Path + "/" + rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
if b == true {
|
||||||
|
t.Error("expected false, got true")
|
||||||
|
}
|
||||||
|
res <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
b, err := c.Exists(c.Path + "/" + rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
if b == true {
|
||||||
|
t.Error("expected false, got true")
|
||||||
|
}
|
||||||
|
res <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
b, err := c.Exists(c.Path + "/" + rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
if b == true {
|
||||||
|
t.Error("expected false, got true")
|
||||||
|
}
|
||||||
|
res <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
b, err := c.Exists(c.Path + "/" + rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
if b == true {
|
||||||
|
t.Error("expected false, got true")
|
||||||
|
}
|
||||||
|
res <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
b, err := c.Exists(c.Path + "/" + rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
if b == true {
|
||||||
|
t.Error("expected false, got true")
|
||||||
|
}
|
||||||
|
res <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-res
|
||||||
|
<-res
|
||||||
|
<-res
|
||||||
|
<-res
|
||||||
|
<-res
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package cheapcash
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func (c *Cache) Delete(key string) error {
|
||||||
|
check, err := c.Exists(c.Path + "/" + key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !check {
|
||||||
|
return ErrNotExists
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(sanitizePath(c.Path + "/" + key))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package cheapcash_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aldy505/cheapcash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
rand := strconv.Itoa(rand.Int())
|
||||||
|
c := cheapcash.Default()
|
||||||
|
err := c.Write(rand, []byte("value"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Delete(rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete_Conccurency(t *testing.T) {
|
||||||
|
rand := strconv.Itoa(rand.Int())
|
||||||
|
c := cheapcash.Default()
|
||||||
|
err := c.Write(rand, []byte("value"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
deleteFunc := func() {
|
||||||
|
err = c.Delete(rand)
|
||||||
|
if err != nil && !errors.Is(err, cheapcash.ErrNotExists) {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(5)
|
||||||
|
go deleteFunc()
|
||||||
|
go deleteFunc()
|
||||||
|
go deleteFunc()
|
||||||
|
go deleteFunc()
|
||||||
|
go deleteFunc()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package cheapcash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cache) Read(key string) ([]byte, error) {
|
||||||
|
check, err := c.Exists(c.Path + key)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !check {
|
||||||
|
return []byte{}, ErrNotExists
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(sanitizePath(c.Path + key))
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package cheapcash_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aldy505/cheapcash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRead(t *testing.T) {
|
||||||
|
rand := strconv.Itoa(rand.Int())
|
||||||
|
c := cheapcash.Default()
|
||||||
|
err := c.Write(rand, []byte("value"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := c.Read(rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
if string(v) != "value" {
|
||||||
|
t.Errorf("expected %s, got %v", "value", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRead_Conccurency(t *testing.T) {
|
||||||
|
rand := strconv.Itoa(rand.Int())
|
||||||
|
c := cheapcash.Default()
|
||||||
|
|
||||||
|
err := c.Write(rand, []byte("value"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
readFunc := func(){
|
||||||
|
r, err := c.Read(rand)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
if string(r) != "value" {
|
||||||
|
t.Error("expected value, got:", string(r))
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(5)
|
||||||
|
go readFunc()
|
||||||
|
go readFunc()
|
||||||
|
go readFunc()
|
||||||
|
go readFunc()
|
||||||
|
go readFunc()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package cheapcash
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type restriction struct {
|
||||||
|
Key string
|
||||||
|
ReplaceWith string
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizePath(path string) string {
|
||||||
|
restricted := []restriction{
|
||||||
|
{Key: " ", ReplaceWith: "_s_"},
|
||||||
|
{Key: "^", ReplaceWith: "_p_"},
|
||||||
|
{Key: "*", ReplaceWith: "_a_"},
|
||||||
|
{Key: "\"", ReplaceWith: "_dq_"},
|
||||||
|
{Key: "'", ReplaceWith: "_sq_"},
|
||||||
|
{Key: "?", ReplaceWith: "_qm_"},
|
||||||
|
{Key: ">", ReplaceWith: "_gt_"},
|
||||||
|
{Key: "<", ReplaceWith: "_lt_"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range restricted {
|
||||||
|
path = strings.ReplaceAll(path, v.Key, v.ReplaceWith)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package cheapcash
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func (c *Cache) Write(key string, value []byte) error {
|
||||||
|
err := checkDir(sanitizePath(c.Path))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
check, err := c.Exists(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if check {
|
||||||
|
err = c.Delete(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(sanitizePath(c.Path + key))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.Write(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package cheapcash_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aldy505/cheapcash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWrite(t *testing.T) {
|
||||||
|
c := cheapcash.Default()
|
||||||
|
err := c.Write("key", []byte("value"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrite_Conccurency(t *testing.T) {
|
||||||
|
c := cheapcash.Default()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
writeFunc := func() {
|
||||||
|
err := c.Write("key1", []byte("value1"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error was thrown:", err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(5)
|
||||||
|
go writeFunc()
|
||||||
|
go writeFunc()
|
||||||
|
go writeFunc()
|
||||||
|
go writeFunc()
|
||||||
|
go writeFunc()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
Loading…
Reference in New Issue