-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsqlitefs.go
129 lines (108 loc) · 3.27 KB
/
sqlitefs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package sqlitefs
import (
"database/sql"
"errors"
"io/fs"
"sync"
)
type writeRequest struct {
path string
data []byte
index int
mimeType string
respCh chan error
}
type SQLiteFS struct {
db *sql.DB
writeCh chan writeRequest
writerWg sync.WaitGroup
}
var _ fs.FS = (*SQLiteFS)(nil)
// NewSQLiteFS создает новый экземпляр SQLiteFS с заданной базой данных.
// Проверяет наличие необходимых таблиц и создает их при отсутствии.
func NewSQLiteFS(db *sql.DB) (*SQLiteFS, error) {
fs := &SQLiteFS{
db: db,
writeCh: make(chan writeRequest),
}
err := fs.createTablesIfNeeded()
if err != nil {
return nil, err
}
fs.writerWg.Add(1)
go fs.writerLoop()
return fs, nil
}
// Open открывает файл по указанному пути.
func (fs *SQLiteFS) Open(name string) (fs.File, error) {
// Проверка существования файла в базе данных
var exists bool
err := fs.db.QueryRow("SELECT EXISTS(SELECT 1 FROM file_metadata WHERE path = ?)", name).Scan(&exists)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.New("file does not exist")
}
// Создание и возврат объекта, реализующего интерфейс File
return NewSQLiteFile(fs.db, name)
}
// createTablesIfNeeded создает таблицы file_metadata и file_fragments, если они еще не созданы.
func (fs *SQLiteFS) createTablesIfNeeded() error {
_, err := fs.db.Exec(`
CREATE TABLE IF NOT EXISTS file_metadata (
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT UNIQUE NOT NULL,
type TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS file_fragments (
file_id INTEGER NOT NULL,
fragment_index INTEGER NOT NULL,
fragment BLOB NOT NULL,
PRIMARY KEY (file_id, fragment_index),
FOREIGN KEY (file_id) REFERENCES file_metadata(id)
);
CREATE INDEX IF NOT EXISTS idx_file_metadata_path ON file_metadata(path);
CREATE INDEX IF NOT EXISTS idx_file_fragments_length ON file_fragments(file_id, length(fragment));
`)
return err
}
func (fs *SQLiteFS) writerLoop() {
defer fs.writerWg.Done()
for req := range fs.writeCh {
var err error
if req.mimeType != "" {
err = fs.createFileRecord(req.path, req.mimeType)
} else {
err = fs.writeFragment(req.path, req.data, req.index)
}
req.respCh <- err
}
}
func (fs *SQLiteFS) createFileRecord(path, mimeType string) error {
_, err := fs.db.Exec("INSERT OR REPLACE INTO file_metadata (path, type) VALUES (?, ?)", path, mimeType)
return err
}
func (fs *SQLiteFS) writeFragment(path string, data []byte, index int) error {
tx, err := fs.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
var fileID int64
err = tx.QueryRow("SELECT id FROM file_metadata WHERE path = ?", path).Scan(&fileID)
if err != nil {
return err
}
_, err = tx.Exec("INSERT OR REPLACE INTO file_fragments (file_id, fragment_index, fragment) VALUES (?, ?, ?)",
fileID, index, data)
if err != nil {
return err
}
return tx.Commit()
}
func (fs *SQLiteFS) Close() error {
close(fs.writeCh)
fs.writerWg.Wait()
return fs.db.Close()
}