-
Notifications
You must be signed in to change notification settings - Fork 0
/
sqlfsync.go
133 lines (108 loc) · 3.14 KB
/
sqlfsync.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
130
131
132
133
package sqlfsync
import (
"errors"
"log"
"reflect"
"github.com/fsnotify/fsnotify"
"gorm.io/gorm"
)
// SqlFSync watches a set of directories for created or deleted files,
// and inserts or deletes from the database, respectively.
type SqlFSync struct {
// pointer to database connection
DB *gorm.DB
// list of directories to watch
Watches []WatchEntry
}
// WatchEntry represents a single directory
type WatchEntry struct {
// full path to directory
Path string
// Pointer to struct that represents
// the entity in the database
Model interface{}
// Watches Path for create or delete events
FSWatcher *fsnotify.Watcher
}
// Create a new instance of SqlFSync
func New(db *gorm.DB) *SqlFSync {
return &SqlFSync{DB: db}
}
// Stop watching all directories
func (sfs *SqlFSync) Close() {
for _, we := range sfs.Watches {
we.FSWatcher.Close()
}
}
// Start watching the specified path for create or delete events.
// The model argument must be a pointer to a struct.
// It must have a field tagged with sqlfsync:"path".
func (sfs *SqlFSync) AddWatch(path string, model interface{}) error {
// model argument must be a pointer to a struct
v := reflect.ValueOf(model)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return errors.New("model argument must be a pointer to a struct")
}
// Check the model has a "Path" field,
// or a struct field tag indicating
// which field holds the file path
e := v.Elem()
t := e.Type()
var structFieldName string
for i := 0; i < t.NumField(); i++ {
tag := t.Field(i).Tag
if tv, ok := tag.Lookup("sqlfsync"); ok && tv == "path" {
structFieldName = t.Field(i).Name
}
}
fieldType, _ := t.FieldByName(structFieldName)
if structFieldName == "" || fieldType.Type.Kind() != reflect.String {
return errors.New("model must have struct tag sqlfsync:\"path\" with type string")
}
// Create fsnotify.Watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
watcher.Add(path)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
continue
}
log.Println("event:", event)
if event.Op == fsnotify.Create {
log.Println("created file:", event.Name)
// Create copy of model and set "path" field to event.Name
new := reflect.New(t)
new.Elem().FieldByName(structFieldName).SetString(event.Name)
// Insert into database
tx := sfs.DB.Create(new.Interface())
// What if there is an error?
if tx.Error != nil {
log.Println(tx.Error)
}
} else if event.Op == fsnotify.Remove {
log.Println("removed file: ", event.Name)
// Create filt copy of model for filtering purposes
filt := reflect.New(t)
filt.Elem().FieldByName(structFieldName).SetString(event.Name)
// Create copy of model to put to-be-deleted entry into
toDelete := reflect.New(t).Interface()
sfs.DB.Where(filt).Find(toDelete)
sfs.DB.Delete(toDelete)
}
case err, ok := <-watcher.Errors:
if !ok {
continue
}
log.Println("error:", err)
}
}
}()
watch := WatchEntry{Path: path, Model: model, FSWatcher: watcher}
sfs.Watches = append(sfs.Watches, watch)
return nil
}