Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ vendor:
## build/server: build cmd/server
build/server:
@echo 'Compiling server...'
go build -ldflags="-X main.serverName=${SERVER_NAME} -X main.port=${PORT}" -o=./bin/pulse-server ./cmd/server
go build -ldflags="-X main.serverName=${SERVER_NAME} -X main.port=${PORT} -X main.uri=${URI} -X main.db=${DB}" -o=./bin/pulse-server ./cmd/server
.PHONY:build/server

## build/client: build cmd/client
Expand All @@ -77,12 +77,6 @@ build/client:
go build -ldflags="-X main.serverName=${SERVER_NAME} -X main.port=${PORT} -X main.hostname=${HOSTNAME}" -o=./bin/pulse-client ./cmd/client
.PHONY:build/client

## build/aggregate: build cmd/aggregate
build/aggregate:
@echo 'Compiling aggregate...'
go build -ldflags="-X main.uri=${URI} -X main.db=${DB}" -o=./bin/pulse-aggregate ./cmd/aggregate
.PHONY:build/aggregate

## build: builds the server and client applications
build: audit build/server build/client build/aggregate
build: audit build/server build/client
.PHONY:build
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ has been a really fun project!
This repository includes the foundation of the project:
- `rpc server`
- `rpc client`
- `log-structured KV store`
- `nvim plugin`
- `cli`

Expand All @@ -31,16 +32,13 @@ initiation of new `nvim` processes, etc:

https://github.com/creativecreature/pulse/assets/12787673/c1cc1dcb-47c3-48c4-a694-056e79f186fe

The buffers I edit are written to an append-only log-structured key-value store
which performs compaction and auto-segmentation. Every segment is roughly 10KB
on disk.

As you can see in the video above, each instance of neovim establishes a new
coding session. This leads to the creation of several sessions per day. Every
session is stored temporarily on the file system. This is primarily to avoid
surpassing any limits set by free database tiers.

I use the CLI to aggregate these temporary sessions by day, week, month, and
year. The results are written to permanent storage, enabling me to retrieve
and display them on my website.

Every 15 minutes, the server requests all buffers from the KV store, and writes
them to a remote mongodb database. I'm doing this primarily to avoid surpassing
any limits set by the free tier.

[1]: https://conner.dev
[2]: ./screenshots/website1.png
Expand Down
22 changes: 0 additions & 22 deletions aggregated_file.go

This file was deleted.

36 changes: 0 additions & 36 deletions aggregated_files.go

This file was deleted.

27 changes: 0 additions & 27 deletions aggregated_session.go

This file was deleted.

38 changes: 0 additions & 38 deletions aggregated_sessions.go

This file was deleted.

76 changes: 45 additions & 31 deletions buffer.go
Original file line number Diff line number Diff line change
@@ -1,52 +1,66 @@
package pulse

import "time"
import (
"cmp"
"fmt"
"time"
)

// Buffer represents a buffer that has been opened during an active coding session.
// Buffer rerpresents a buffer that has been edited during a coding session.
type Buffer struct {
OpenedClosed []time.Time
Filename string
Repository string
Filepath string
Filetype string
OpenedAt time.Time `json:"-"`
ClosedAt time.Time `json:"-"`
Duration time.Duration `json:"duration"`
Filename string `json:"filename"`
Filepath string `json:"filepath"`
Filetype string `json:"filetype"`
Repository string `json:"repository"`
}

// NewBuffer creates a new buffer.
func NewBuffer(filename, repo, filetype, filepath string, openedAt time.Time) Buffer {
return Buffer{
OpenedClosed: []time.Time{openedAt},
Filename: filename,
Repository: repo,
Filetype: filetype,
Filepath: filepath,
OpenedAt: openedAt,
Filename: filename,
Filepath: filepath,
Filetype: filetype,
Repository: repo,
}
}

// Open should be called when a buffer is opened.
func (b *Buffer) Open(time time.Time) {
b.OpenedClosed = append(b.OpenedClosed, time)
// Close should be called when the coding session ends, or another buffer is opened.
func (b *Buffer) Close(closedAt time.Time) {
b.ClosedAt = closedAt
b.Duration = b.ClosedAt.Sub(b.OpenedAt)
}

// IsOpen returns true if the given buffer is currently open.
func (b *Buffer) IsOpen() bool {
return len(b.OpenedClosed)%2 == 1
// Key returns a unique identifier for the buffer.
func (b *Buffer) Key() string {
return fmt.Sprintf("%s_%s_%s", b.OpenedAt.Format("2006-01-02"), b.Repository, b.Filepath)
}

// LastOpened returns the last time the buffer was opened.
func (b *Buffer) LastOpened() time.Time {
return b.OpenedClosed[len(b.OpenedClosed)-1]
// Merge takes two buffers, merges them, and returns the result.
func (b *Buffer) Merge(other Buffer) Buffer {
return Buffer{
Filename: cmp.Or(b.Filename, other.Filename),
Filepath: cmp.Or(b.Filepath, other.Filepath),
Filetype: cmp.Or(b.Filetype, other.Filetype),
Repository: cmp.Or(b.Repository, other.Repository),
Duration: b.Duration + other.Duration,
}
}

// Buffers represents a slice of buffers that have been edited during a coding session.
type Buffers []Buffer

func (b Buffers) Len() int {
return len(b)
}

// Close should be called when a buffer is closed.
func (b *Buffer) Close(time time.Time) {
b.OpenedClosed = append(b.OpenedClosed, time)
func (b Buffers) Less(i, j int) bool {
return b[i].OpenedAt.Before(b[j].OpenedAt)
}

// Duration returns the total duration that the buffer has been open.
func (b *Buffer) Duration() time.Duration {
var duration time.Duration
for i := 0; i < len(b.OpenedClosed); i += 2 {
duration += b.OpenedClosed[i+1].Sub(b.OpenedClosed[i])
}
return duration
func (b Buffers) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
49 changes: 0 additions & 49 deletions buffer_stack.go

This file was deleted.

63 changes: 0 additions & 63 deletions cmd/aggregate/main.go

This file was deleted.

Loading