Skip to content
This repository has been archived by the owner on Aug 3, 2024. It is now read-only.

refactor: folders of madness #34

Merged
merged 2 commits into from
Aug 10, 2020
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
16 changes: 1 addition & 15 deletions autoscan.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ import (
// The Scan is used across Triggers, Targets and the Processor.
type Scan struct {
Folder string
File string
Priority int
Retries int
Removed bool
Time time.Time
}

Expand All @@ -33,21 +30,10 @@ type HTTPTrigger func(ProcessorFunc) http.Handler
// A Target receives a Scan from the Processor and translates the Scan
// into a format understood by the target.
type Target interface {
Scan([]Scan) error
Scan(Scan) error
Available() error
}

const (
// TVDb provider for use in autoscan.Metadata
TVDb = "tvdb"

// TMDb provider for use in autoscan.Metadata
TMDb = "tmdb"

// IMDb provider for use in autoscan.Metadata
IMDb = "imdb"
)

var (
// ErrTargetUnavailable may occur when a Target goes offline
// or suffers from fatal errors. In this case, the processor
Expand Down
8 changes: 3 additions & 5 deletions cmd/autoscan/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,21 @@ import (
"path/filepath"
"time"

"github.com/cloudbox/autoscan/targets/emby"

"github.com/alecthomas/kong"
"github.com/natefinch/lumberjack"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v2"

"github.com/alecthomas/kong"
"github.com/cloudbox/autoscan"
"github.com/cloudbox/autoscan/processor"
"github.com/cloudbox/autoscan/targets/emby"
"github.com/cloudbox/autoscan/targets/plex"
"github.com/cloudbox/autoscan/triggers"
"github.com/cloudbox/autoscan/triggers/bernard"
"github.com/cloudbox/autoscan/triggers/lidarr"
"github.com/cloudbox/autoscan/triggers/radarr"
"github.com/cloudbox/autoscan/triggers/sonarr"
"github.com/natefinch/lumberjack"
)

type config struct {
Expand Down Expand Up @@ -163,7 +162,6 @@ func main() {
proc, err := processor.New(processor.Config{
Anchors: c.Anchors,
DatastorePath: cli.Database,
MaxRetries: c.MaxRetries,
MinimumAge: c.MinimumAge,
})

Expand Down
163 changes: 36 additions & 127 deletions processor/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package processor
import (
"database/sql"
"errors"
"fmt"
"time"

"github.com/cloudbox/autoscan"
Expand All @@ -12,18 +13,15 @@ import (
)

type datastore struct {
db *sql.DB
*sql.DB
}

const sqlSchema = `
CREATE TABLE IF NOT EXISTS scan (
"folder" TEXT NOT NULL,
"file" TEXT NOT NULL,
"priority" INTEGER NOT NULL,
"time" DATETIME NOT NULL,
"retries" INTEGER NOT NULL,
"removed" BOOLEAN NOT NULL,
PRIMARY KEY(folder, file)
PRIMARY KEY(folder)
)
`

Expand All @@ -38,30 +36,26 @@ func newDatastore(path string) (*datastore, error) {
return nil, err
}

store := &datastore{
db: db,
}
store := &datastore{db}

return store, nil
}

const sqlUpsert = `
INSERT INTO scan (folder, file, priority, time, retries, removed)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT (folder, file) DO UPDATE SET
INSERT INTO scan (folder, priority, time)
VALUES (?, ?, ?)
ON CONFLICT (folder) DO UPDATE SET
priority = MAX(excluded.priority, scan.priority),
time = excluded.time,
retries = excluded.retries,
removed = min(excluded.removed, scan.removed)
time = excluded.time
`

func (store datastore) upsert(tx *sql.Tx, scan autoscan.Scan) error {
_, err := tx.Exec(sqlUpsert, scan.Folder, scan.File, scan.Priority, scan.Time, scan.Retries, scan.Removed)
func (store *datastore) upsert(tx *sql.Tx, scan autoscan.Scan) error {
_, err := tx.Exec(sqlUpsert, scan.Folder, scan.Priority, scan.Time)
return err
}

func (store datastore) Upsert(scans []autoscan.Scan) error {
tx, err := store.db.Begin()
func (store *datastore) Upsert(scans []autoscan.Scan) error {
tx, err := store.Begin()
if err != nil {
return err
}
Expand All @@ -79,111 +73,42 @@ func (store datastore) Upsert(scans []autoscan.Scan) error {
return tx.Commit()
}

const sqlGetMatching = `
SELECT folder, file, priority, retries, removed, time FROM scan
WHERE folder = (
SELECT folder
FROM scan
GROUP BY folder
HAVING MAX(time) < ?
ORDER BY priority DESC, time ASC
LIMIT 1
)
const sqlGetAvailableScan = `
SELECT folder, priority, time FROM scan
WHERE time < ?
ORDER BY priority DESC, time ASC
LIMIT 1
`

func (store datastore) GetMatching(minAge time.Duration) (scans []autoscan.Scan, err error) {
rows, err := store.db.Query(sqlGetMatching, now().Add(-1*minAge))
if errors.Is(err, sql.ErrNoRows) {
return scans, nil
}

if err != nil {
return scans, err
}

defer rows.Close()
for rows.Next() {
scan := autoscan.Scan{}
err = rows.Scan(&scan.Folder, &scan.File, &scan.Priority, &scan.Retries, &scan.Removed, &scan.Time)
if err != nil {
return scans, err
}
func (store *datastore) GetAvailableScan(minAge time.Duration) (autoscan.Scan, error) {
row := store.QueryRow(sqlGetAvailableScan, now().Add(-1*minAge))

scans = append(scans, scan)
scan := autoscan.Scan{}
err := row.Scan(&scan.Folder, &scan.Priority, &scan.Time)
switch {
case errors.Is(err, sql.ErrNoRows):
return scan, autoscan.ErrNoScans
case err != nil:
return scan, fmt.Errorf("get matching: %s: %w", err, autoscan.ErrFatal)
}

return scans, rows.Err()
}

const sqlIncrementRetries = `
UPDATE scan
SET retries = retries + 1, time = ?
WHERE folder = ?
`

// Increment the retry count of all the children of a folder.
// Furthermore, we also update the timestamp to the current time
// so the children will not get scanned for 5 minutes.
func (store datastore) incrementRetries(tx *sql.Tx, folder string) error {
_, err := tx.Exec(sqlIncrementRetries, now(), folder)
return err
}

const sqlDeleteRetries = `
DELETE FROM scan
WHERE folder = ? AND retries > ?
`

func (store datastore) deleteRetries(tx *sql.Tx, folder string, maxRetries int) error {
_, err := tx.Exec(sqlDeleteRetries, folder, maxRetries)
return err
}

func (store datastore) Retry(folder string, maxRetries int) error {
tx, err := store.db.Begin()
if err != nil {
return err
}

err = store.incrementRetries(tx, folder)
if err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
panic(rbErr)
}

return err
}

err = store.deleteRetries(tx, folder, maxRetries)
if err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
panic(rbErr)
}

return err
}

return tx.Commit()
return scan, nil
}

const sqlGetAll = `
SELECT folder, file, priority, retries, removed, time FROM scan
SELECT folder, priority, time FROM scan
`

func (store datastore) GetAll() (scans []autoscan.Scan, err error) {
rows, err := store.db.Query(sqlGetAll)
if errors.Is(err, sql.ErrNoRows) {
return scans, nil
}

func (store *datastore) GetAll() (scans []autoscan.Scan, err error) {
rows, err := store.Query(sqlGetAll)
if err != nil {
return scans, err
}

defer rows.Close()
for rows.Next() {
scan := autoscan.Scan{}
err = rows.Scan(&scan.Folder, &scan.File, &scan.Priority, &scan.Retries, &scan.Removed, &scan.Time)
err = rows.Scan(&scan.Folder, &scan.Priority, &scan.Time)
if err != nil {
return scans, err
}
Expand All @@ -195,32 +120,16 @@ func (store datastore) GetAll() (scans []autoscan.Scan, err error) {
}

const sqlDelete = `
DELETE FROM scan
WHERE folder=? AND file=?
DELETE FROM scan WHERE folder=?
`

func (store datastore) delete(tx *sql.Tx, scan autoscan.Scan) error {
_, err := tx.Exec(sqlDelete, scan.Folder, scan.File)
return err
}

func (store datastore) Delete(scans []autoscan.Scan) error {
tx, err := store.db.Begin()
func (store *datastore) Delete(scan autoscan.Scan) error {
_, err := store.Exec(sqlDelete, scan.Folder)
if err != nil {
return err
return fmt.Errorf("delete: %s: %w", err, autoscan.ErrFatal)
}

for _, scan := range scans {
if err = store.delete(tx, scan); err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
panic(rollbackErr)
}

return err
}
}

return tx.Commit()
return nil
}

var now = time.Now
Loading