Skip to content

Commit

Permalink
Merge pull request #15 from sonroyaalmerol/14-remove-redis-as-a-depen…
Browse files Browse the repository at this point in the history
…dency

Swap redis with memdb and remove cgo sqlite
  • Loading branch information
sonroyaalmerol authored Mar 5, 2024
2 parents 8c6dc91 + 16ee5c8 commit 4c7d075
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 161 deletions.
25 changes: 4 additions & 21 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,19 @@ RUN go mod download
# Copy the source code from the current directory to the Working Directory inside the container
COPY . .

# Build the Go app
ENV CGO_ENABLED=1

# hadolint ignore=DL3018
RUN apk add --no-cache gcc musl-dev \
&& go build -ldflags='-s -w -extldflags "-static"' -o main .

# Run tests
RUN go test ./...
RUN go build -ldflags='-s -w' -o main . \
&& go test ./...

####################

# Start a new stage from scratch
FROM alpine:3.19.1

# Install Redis
# hadolint ignore=DL3018
RUN apk --no-cache add redis
FROM scratch

# Copy the built Go binary from the previous stage
COPY --from=build /app/main /gomain

# Expose ports for Go application and Redis
EXPOSE 8080

# Copy the entrypoint script
COPY entrypoint.sh /

# Set execute permission on the entrypoint script
RUN chmod +x /entrypoint.sh

# Run the entrypoint script
CMD ["/entrypoint.sh"]
CMD ["/gomain"]
47 changes: 47 additions & 0 deletions database/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,24 @@ func TestSaveAndLoadFromSQLite(t *testing.T) {

// Test LoadFromSQLite with existing data in the database
expected := []StreamInfo{{
DbId: 1,
Title: "stream1",
TvgID: "test1",
LogoURL: "http://test.com/image.png",
Group: "test",
URLs: []StreamURL{{
DbId: 1,
Content: "testing",
M3UIndex: 1,
}},
}, {
DbId: 2,
Title: "stream2",
TvgID: "test2",
LogoURL: "http://test2.com/image.png",
Group: "test2",
URLs: []StreamURL{{
DbId: 2,
Content: "testing2",
M3UIndex: 2,
}},
Expand Down Expand Up @@ -57,12 +61,18 @@ func TestSaveAndLoadFromSQLite(t *testing.T) {
t.Errorf("DeleteStreamByTitle returned error: %v", err)
}

err = DeleteStreamURL(db, expected[0].URLs[0].DbId)
if err != nil {
t.Errorf("DeleteStreamURL returned error: %v", err)
}

result, err = GetStreams(db)
if err != nil {
t.Errorf("GetStreams returned error: %v", err)
}

expected = expected[:1]
expected[0].URLs = make([]StreamURL, 0)

if len(result) != len(expected) {
t.Errorf("GetStreams returned %+v, expected %+v", result, expected)
Expand Down Expand Up @@ -94,3 +104,40 @@ func streamInfoEqual(a, b StreamInfo) bool {

return true
}

func TestConcurrency(t *testing.T) {
// Initialize the in-memory database
err := InitializeMemDB()
if err != nil {
t.Errorf("Error initializing in-memory database: %v", err)
}

// Test IncrementConcurrency and GetConcurrency
m3uIndex := 1
err = IncrementConcurrency(m3uIndex)
if err != nil {
t.Errorf("Error incrementing concurrency: %v", err)
}

count, err := GetConcurrency(m3uIndex)
if err != nil {
t.Errorf("Error getting concurrency: %v", err)
}
if count != 1 {
t.Errorf("Expected concurrency count to be 1, got %d", count)
}

// Test DecrementConcurrency
err = DecrementConcurrency(m3uIndex)
if err != nil {
t.Errorf("Error decrementing concurrency: %v", err)
}

count, err = GetConcurrency(m3uIndex)
if err != nil {
t.Errorf("Error getting concurrency: %v", err)
}
if count != 0 {
t.Errorf("Expected concurrency count to be 0, got %d", count)
}
}
129 changes: 93 additions & 36 deletions database/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,75 @@ import (
"log"
"os"
"path/filepath"
"strings"
"sync"

_ "github.com/mattn/go-sqlite3"
_ "modernc.org/sqlite"
)

var mutex sync.Mutex

func checkAndUpdateTable(db *sql.DB, tableName string, expectedColumns map[string]string, foreignKeys map[string]string) error {
// Check table existence
var count int
err := db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", tableName).Scan(&count)
if err != nil {
return fmt.Errorf("error checking %s table: %v\n", tableName, err)
}

if count > 0 {
// Table exists, check structure
rows, err := db.Query("PRAGMA table_info(" + tableName + ")")
if err != nil {
return fmt.Errorf("error retrieving table info for %s: %v\n", tableName, err)
}
defer rows.Close()

existingColumns := make(map[string]string)
for rows.Next() {
var cid int
var name, _type string
var notnull, pk int
var dflt_value interface{}
err = rows.Scan(&cid, &name, &_type, &notnull, &dflt_value, &pk)
if err != nil {
return fmt.Errorf("error scanning row: %v\n", err)
}
existingColumns[name] = _type
}

// Check if column names and types match expected structure
for col, dataType := range expectedColumns {
if existingType, ok := existingColumns[col]; !ok || existingType != dataType {
// Table structure doesn't match, drop and recreate
_, err = db.Exec("DROP TABLE " + tableName)
if err != nil {
return fmt.Errorf("error dropping %s table: %v\n", tableName, err)
}
break
}
}
}

// Create table if not exists or if dropped due to structure mismatch
query := "CREATE TABLE IF NOT EXISTS " + tableName + " ("
for col, dataType := range expectedColumns {
query += col + " " + dataType + ","
}
if len(foreignKeys) > 0 {
for fk := range foreignKeys {
query += " " + fk + ","
}
}
query = strings.TrimSuffix(query, ",") + ")"
_, err = db.Exec(query)
if err != nil {
return fmt.Errorf("error creating %s table: %v\n", tableName, err)
}

return nil
}

func InitializeSQLite(name string) (db *sql.DB, err error) {
mutex.Lock()
defer mutex.Unlock()
Expand All @@ -38,37 +100,32 @@ func InitializeSQLite(name string) (db *sql.DB, err error) {
}
file.Close()

db, err = sql.Open("sqlite3", filename)
db, err = sql.Open("sqlite", filename)
if err != nil {
return nil, fmt.Errorf("error opening SQLite database: %v\n", err)
}

// Create table if not exists
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS streams (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT UNIQUE,
tvg_id TEXT,
logo_url TEXT,
group_name TEXT
)
`)
if err != nil {
return nil, fmt.Errorf("error creating table: %v\n", err)
}

_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS stream_urls (
id INTEGER PRIMARY KEY AUTOINCREMENT,
stream_id INTEGER,
content TEXT,
m3u_index INTEGER,
max_concurrency INTEGER DEFAULT 1,
FOREIGN KEY(stream_id) REFERENCES streams(id)
)
`)
if err != nil {
return nil, fmt.Errorf("error creating table: %v\n", err)
// Check and update 'streams' table
if err := checkAndUpdateTable(db, "streams", map[string]string{
"id": "INTEGER PRIMARY KEY AUTOINCREMENT",
"title": "TEXT UNIQUE",
"tvg_id": "TEXT",
"logo_url": "TEXT",
"group_name": "TEXT",
}, nil); err != nil {
return nil, err
}

// Check and update 'stream_urls' table
if err := checkAndUpdateTable(db, "stream_urls", map[string]string{
"id": "INTEGER PRIMARY KEY AUTOINCREMENT",
"stream_id": "INTEGER",
"content": "TEXT",
"m3u_index": "INTEGER",
}, map[string]string{
"FOREIGN KEY(stream_id) REFERENCES streams(id)": "",
}); err != nil {
return nil, err
}

return
Expand Down Expand Up @@ -207,13 +264,13 @@ func InsertStreamUrl(db *sql.DB, id int64, url StreamURL) (i int64, err error) {
}
}()

urlStmt, err := tx.Prepare("INSERT INTO stream_urls(stream_id, content, m3u_index, max_concurrency) VALUES(?, ?, ?, ?)")
urlStmt, err := tx.Prepare("INSERT INTO stream_urls(stream_id, content, m3u_index) VALUES(?, ?, ?)")
if err != nil {
return -1, fmt.Errorf("error preparing statement: %v", err)
}
defer urlStmt.Close()

res, err := urlStmt.Exec(id, url.Content, url.M3UIndex, url.MaxConcurrency)
res, err := urlStmt.Exec(id, url.Content, url.M3UIndex)
if err != nil {
return -1, fmt.Errorf("error inserting stream URL: %v", err)
}
Expand Down Expand Up @@ -313,7 +370,7 @@ func GetStreamByTitle(db *sql.DB, title string) (s StreamInfo, err error) {
return s, fmt.Errorf("error scanning stream: %v", err)
}

urlRows, err := db.Query("SELECT id, content, m3u_index, max_concurrency FROM stream_urls WHERE stream_id = ?", s.DbId)
urlRows, err := db.Query("SELECT id, content, m3u_index FROM stream_urls WHERE stream_id = ?", s.DbId)
if err != nil {
return s, fmt.Errorf("error querying stream URLs: %v", err)
}
Expand All @@ -322,7 +379,7 @@ func GetStreamByTitle(db *sql.DB, title string) (s StreamInfo, err error) {
var urls []StreamURL
for urlRows.Next() {
var u StreamURL
err := urlRows.Scan(&u.DbId, &u.Content, &u.M3UIndex, &u.MaxConcurrency)
err := urlRows.Scan(&u.DbId, &u.Content, &u.M3UIndex)
if err != nil {
return s, fmt.Errorf("error scanning stream URL: %v", err)
}
Expand All @@ -349,14 +406,14 @@ func GetStreamUrlByUrlAndIndex(db *sql.DB, url string, m3u_index int) (s StreamU
mutex.Lock()
defer mutex.Unlock()

rows, err := db.Query("SELECT id, content, m3u_index, max_concurrency FROM stream_urls WHERE content = ? AND m3u_index = ?", url, m3u_index)
rows, err := db.Query("SELECT id, content, m3u_index FROM stream_urls WHERE content = ? AND m3u_index = ?", url, m3u_index)
if err != nil {
return s, fmt.Errorf("error querying streams: %v", err)
}
defer rows.Close()

for rows.Next() {
err = rows.Scan(&s.DbId, &s.Content, &s.M3UIndex, &s.MaxConcurrency)
err = rows.Scan(&s.DbId, &s.Content, &s.M3UIndex)
if err != nil {
return s, fmt.Errorf("error scanning stream: %v", err)
}
Expand Down Expand Up @@ -387,7 +444,7 @@ func GetStreams(db *sql.DB) ([]StreamInfo, error) {
return nil, fmt.Errorf("error scanning stream: %v", err)
}

urlRows, err := db.Query("SELECT id, content, m3u_index, max_concurrency FROM stream_urls WHERE stream_id = ?", s.DbId)
urlRows, err := db.Query("SELECT id, content, m3u_index FROM stream_urls WHERE stream_id = ?", s.DbId)
if err != nil {
return nil, fmt.Errorf("error querying stream URLs: %v", err)
}
Expand All @@ -396,7 +453,7 @@ func GetStreams(db *sql.DB) ([]StreamInfo, error) {
var urls []StreamURL
for urlRows.Next() {
var u StreamURL
err := urlRows.Scan(&u.DbId, &u.Content, &u.M3UIndex, &u.MaxConcurrency)
err := urlRows.Scan(&u.DbId, &u.Content, &u.M3UIndex)
if err != nil {
return nil, fmt.Errorf("error scanning stream URL: %v", err)
}
Expand Down
Loading

0 comments on commit 4c7d075

Please sign in to comment.