11package filedrop
22
3- import "database/sql"
3+ import (
4+ "database/sql"
5+ "strings"
6+ "time"
7+ )
48
59type db struct {
610 * sql.DB
11+
12+ addFile * sql.Stmt
13+ remFile * sql.Stmt
14+ getFile * sql.Stmt
15+
16+ addUse * sql.Stmt
17+ shouldDelete * sql.Stmt
718}
819
920func openDB (driver , dsn string ) (* db , error ) {
10- return & db {}, nil
21+ if driver == "sqlite3" {
22+ // We apply some tricks for SQLite to avoid "database is locked" errors.
23+
24+ if ! strings .HasPrefix (dsn , "file:" ) {
25+ dsn = "file:" + dsn
26+ }
27+ if ! strings .Contains (dsn , "?" ) {
28+ dsn = dsn + "?"
29+ }
30+ dsn = dsn + "cache=shared&_journal=WAL&_busy_timeout=5000"
31+ }
32+
33+ db := new (db )
34+ var err error
35+ db .DB , err = sql .Open (driver , dsn )
36+ if err != nil {
37+ return nil , err
38+ }
39+
40+
41+ if driver == "sqlite3" {
42+ // Also some optimizations for SQLite to make it FAA-A-A-AST.
43+ db .Exec (`PRAGMA foreign_keys = ON` )
44+ db .Exec (`PRAGMA auto_vacuum = INCREMENTAL` )
45+ db .Exec (`PRAGMA journal_mode = WAL` )
46+ db .Exec (`PRAGMA defer_foreign_keys = ON` )
47+ db .Exec (`PRAGMA synchronous = NORMAL` )
48+ db .Exec (`PRAGMA temp_store = MEMORY` )
49+ db .Exec (`PRAGMA cache_size = 5000` )
50+ }
51+
52+ if err := db .initSchema (); err != nil {
53+ panic (err )
54+ }
55+ if err := db .initStmts (); err != nil {
56+ panic (err )
57+ }
58+ return db , nil
1159}
60+
61+ func (db * db ) initSchema () error {
62+ _ , err := db .Exec (`CREATE TABLE IF NOT EXISTS filedrop (
63+ uuid TEXT PRIMARY KEY NOT NULL,
64+ uses INTEGER NOT NULL DEFAULT 0,
65+ maxUses INTEGER,
66+ storeUntil INTEGER
67+ )` )
68+ if err != nil {
69+ return err
70+ }
71+ return nil
72+ }
73+
74+ func (db * db ) initStmts () error {
75+ var err error
76+ db .addFile , err = db .Prepare (`INSERT INTO filedrop(uuid, maxUses, storeUntil) VALUES (?, ?, ?)` )
77+ if err != nil {
78+ return err
79+ }
80+ db .remFile , err = db .Prepare (`DELETE FROM filedrop WHERE uuid = ?` )
81+ if err != nil {
82+ return err
83+ }
84+ db .getFile , err = db .Prepare (`SELECT uses, maxUses, storeUntil FROM filedrop WHERE uuid = ?` )
85+ if err != nil {
86+ return err
87+ }
88+ db .shouldDelete , err = db .Prepare (`SELECT exists(SELECT uuid FROM filedrop WHERE uuid = ? AND (storeUntil < ? OR maxUses == uses))` )
89+ if err != nil {
90+ return err
91+ }
92+ db .addUse , err = db .Prepare (`UPDATE filedrop SET uses = (SELECT uses+1 FROM filedrop WHERE uuid = ?) WHERE uuid = ?` )
93+ if err != nil {
94+ return err
95+ }
96+ return nil
97+ }
98+
99+ func (db * db ) AddFile (tx * sql.Tx , uuid string , maxUses uint , storeUntil time.Time ) error {
100+ maxUsesN := sql.NullInt64 {Int64 : int64 (maxUses ), Valid : maxUses != 0 }
101+ storeUntilN := sql.NullInt64 {Int64 : storeUntil .Unix (), Valid : ! storeUntil .IsZero ()}
102+
103+ if tx != nil {
104+ _ , err := tx .Stmt (db .addFile ).Exec (uuid , maxUsesN , storeUntilN )
105+ return err
106+ } else {
107+ _ , err := db .addFile .Exec (uuid , maxUsesN , storeUntilN )
108+ return err
109+ }
110+ }
111+
112+ func (db * db ) RemoveFile (tx * sql.Tx , uuid string ) error {
113+ if tx != nil {
114+ _ , err := tx .Stmt (db .remFile ).Exec (uuid )
115+ return err
116+ } else {
117+ _ , err := db .remFile .Exec (uuid )
118+ return err
119+ }
120+ }
121+
122+ func (db * db ) ShouldDelete (tx * sql.Tx , uuid string ) bool {
123+ var row * sql.Row
124+ if tx != nil {
125+ row = tx .Stmt (db .shouldDelete ).QueryRow (uuid , time .Now ().Unix ())
126+ } else {
127+ row = db .shouldDelete .QueryRow (uuid , time .Now ().Unix ())
128+ }
129+ res := 0
130+ if err := row .Scan (& res ); err != nil {
131+ return false
132+ }
133+ return res == 1
134+ }
135+
136+ func (db * db ) AddUse (tx * sql.Tx , uuid string ) error {
137+ if tx != nil {
138+ _ , err := tx .Stmt (db .addUse ).Exec (uuid , uuid )
139+ return err
140+ } else {
141+ _ , err := db .addUse .Exec (uuid , uuid )
142+ return err
143+ }
144+ }
0 commit comments