diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..0e43d0a --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,26 @@ +on: + push: + tags: + - 'v*.*.*' +name: Build +jobs: + build: + strategy: + matrix: + go-version: [1.13.x] + platform: [ubuntu-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Checkout code + uses: actions/checkout@v1 + - name: Set env + run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF:10} + - name: Build + run: docker build + -t tomwright/mermaid-server:latest \ + -t tomwright/mermaid-server:${{ env.RELEASE_VERSION }} \ + -f Dockerfile . + - name: Login + run: echo ${{ secrets.DOCKER_PASS }} | docker login -u${{ secrets.DOCKER_USER }} --password-stdin + - name: Push + run: docker push tomwright/mermaid-server:latest && docker push tomwright/mermaid-server:${{ env.RELEASE_VERSION }} \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..a177814 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,24 @@ +on: [push, pull_request] +name: Test +jobs: + test: + strategy: + matrix: + go-version: [1.13.x] + platform: [ubuntu-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Install Go + uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v1 + - uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Test + run: go test -race ./... \ No newline at end of file diff --git a/internal/cache.go b/internal/cache.go index 2c57da7..78ccf6f 100644 --- a/internal/cache.go +++ b/internal/cache.go @@ -10,6 +10,10 @@ type DiagramCache interface { Has(diagram *Diagram) (bool, error) // Get returns a cached version of the given diagram description. Get(diagram *Diagram) (*Diagram, error) + // GetAll returns all of the cached diagrams. + GetAll() ([]*Diagram, error) + // Delete deletes a cached version of the given diagram. + Delete(diagram *Diagram) error } // NewDiagramCache returns an implementation of DiagramCache. @@ -57,3 +61,27 @@ func (c *inMemoryDiagramCache) Get(diagram *Diagram) (*Diagram, error) { } return nil, nil } + +// GetAll returns all of the cached diagrams. +func (c *inMemoryDiagramCache) GetAll() ([]*Diagram, error) { + res := make([]*Diagram, len(c.idToDiagram)) + i := 0 + for _, diagram := range c.idToDiagram { + res[i] = diagram + i++ + } + return res, nil +} + +// Delete deletes a cached version of the given diagram. +func (c *inMemoryDiagramCache) Delete(diagram *Diagram) error { + id, err := diagram.ID() + if err != nil { + return err + } + if _, ok := c.idToDiagram[id]; ok { + delete(c.idToDiagram, id) + return nil + } + return nil +} diff --git a/internal/diagram.go b/internal/diagram.go index 3d73de9..32c2fe3 100644 --- a/internal/diagram.go +++ b/internal/diagram.go @@ -5,22 +5,45 @@ import ( "encoding/base64" "encoding/hex" "strings" + "sync" + "time" ) // NewDiagram returns a new diagram. func NewDiagram(description []byte) *Diagram { return &Diagram{ description: []byte(strings.TrimSpace(string(description))), + lastTouched: time.Now(), + mu: &sync.RWMutex{}, } } +// Diagram represents a single diagram. type Diagram struct { - // iD is the ID of the Diagram + // id is the ID of the Diagram id string // description is the description of the diagram. description []byte // Output is the filepath to the output file. Output string + // mu is a mutex to protect the last touched value. + mu *sync.RWMutex + // lastTouched is the time that the diagram was last used. + lastTouched time.Time +} + +// Touch updates the last touched time of the diagram. +func (d *Diagram) Touch() { + d.mu.Lock() + defer d.mu.Unlock() + d.lastTouched = time.Now() +} + +// TouchedInDuration returns true if the diagram has been touched in the given duration. +func (d *Diagram) TouchedInDuration(duration time.Duration) bool { + d.mu.Lock() + defer d.mu.Unlock() + return time.Now().Add(-duration).Before(d.lastTouched) } // ID returns an ID for the diagram. diff --git a/internal/generator.go b/internal/generator.go index 1468edc..64128ea 100644 --- a/internal/generator.go +++ b/internal/generator.go @@ -51,8 +51,17 @@ func (c cachingGenerator) Generate(diagram *Diagram) error { return fmt.Errorf("cache.Get failed: %w", err) } *diagram = *cached + + // Update diagram last touched date + diagram.Touch() + if err := c.cache.Store(diagram); err != nil { + return fmt.Errorf("cache.Store failed: %w", err) + } + return nil } + + diagram.Touch() if err := c.generate(diagram); err != nil { return fmt.Errorf("cachingGenerater.generate failed: %w", err) } @@ -109,6 +118,38 @@ func (c cachingGenerator) generate(diagram *Diagram) error { // CleanUp removes any diagrams that haven't used within the given duration. func (c cachingGenerator) CleanUp(duration time.Duration) error { - // todo : loop through all cached diagrams and delete any that haven't been used within duration. + diagrams, err := c.cache.GetAll() + if err != nil { + return fmt.Errorf("could not get cached diagrams: %w", err) + } + for _, d := range diagrams { + if !d.TouchedInDuration(duration) { + if err := c.delete(d); err != nil { + return fmt.Errorf("could not delete diagram: %w", err) + } + } + } + return nil +} + +// delete removes any diagrams that haven't used within the given duration. +func (c cachingGenerator) delete(diagram *Diagram) error { + id, err := diagram.ID() + if err != nil { + return fmt.Errorf("cannot get diagram ID: %w", err) + } + inPath := fmt.Sprintf("%s/%s.mmd", c.inPath, id) + outPath := fmt.Sprintf("%s/%s.svg", c.outPath, id) + + if err := os.Remove(inPath); err != nil { + return fmt.Errorf("could not delete diagram input: %w", err) + } + if err := os.Remove(outPath); err != nil { + return fmt.Errorf("could not delete diagram output: %w", err) + } + if err := c.cache.Delete(diagram); err != nil { + return fmt.Errorf("could not remove diagram from cache: %w", err) + } + return nil }