This repository has been archived by the owner on Mar 9, 2022. It is now read-only.
/
sync.go
244 lines (235 loc) · 5.64 KB
/
sync.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package main
import (
"github.com/rjeczalik/notify"
"path/filepath"
"os"
"errors"
"strings"
"io"
"strconv"
)
type Sync struct {
containerFiler ContainerFiler
sourceDir string
targetDir string
eventChan chan notify.EventInfo
fileToRenamed string
swapping bool
forceSync bool
}
var ignoredExts []string = []string{"swp", "swx"}
func NewSync(containerFiler ContainerFiler, sourceDir, targetDir string) (*Sync, error) {
fi, err := os.Stat(sourceDir)
if err != nil {
return nil, err
}
if !fi.IsDir() {
return nil, errors.New("You must pass a directory, not a file in source dir")
}
sourceDir = strings.TrimSuffix(sourceDir, "/")
return &Sync{
containerFiler: containerFiler,
sourceDir: sourceDir,
targetDir: targetDir,
eventChan: make(chan notify.EventInfo, 50),
}, nil
}
func (s *Sync) Run() error {
err := s.syncFolder()
if err != nil {
return err
}
logger.Info("Start watching for change in folder '%s'\n", TruncatePath(s.sourceDir))
if err := notify.Watch(s.sourceDir + "/...", s.eventChan, notify.Remove, notify.Create, notify.Write, notify.Rename); err != nil {
return err
}
defer notify.Stop(s.eventChan)
// Block until an event is received.
for ei := range s.eventChan {
if s.isIgnored(ei.Path()) {
continue
}
logger.Info("Received event: '%s' for file '%s'", ei.Event().String(), TruncatePath(ei.Path()))
err = s.action(ei)
if err != nil {
logger.Error("Event has errored: " + err.Error())
}
}
return nil
}
func (s Sync) isIgnored(path string) bool {
ext := filepath.Ext(path)
for _, ignoredExt := range ignoredExts {
if ext == "." + ignoredExt {
return true
}
}
_, err := strconv.Atoi(filepath.Base(path))
if err == nil {
return true
}
return false
}
func (s *Sync) syncFolder() error {
dirIsEmpty, err := s.DirIsEmpty(s.sourceDir)
if err != nil {
return err
}
if !dirIsEmpty && !s.forceSync {
logger.Info("No need to synchronize from remote, directory not empty.")
return nil
}
logger.Info("Synchronizing folder '%s' from the remote folder '%s' ...", TruncatePath(s.sourceDir), TruncatePath(s.targetDir))
err = s.containerFiler.CopyRemoteFolder(s.sourceDir, s.targetDir)
if err != nil {
return err
}
logger.Info("Synchronization finished.\n")
return nil
}
func (s Sync) DirIsEmpty(name string) (bool, error) {
f, err := os.Open(name)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1)
if err == io.EOF {
return true, nil
}
return false, err
}
func (s Sync) getFile(path string) (*os.File, os.FileInfo, error) {
stat, err := os.Stat(path)
if err != nil {
return nil, stat, err
}
f, err := os.Open(path)
if err != nil {
return nil, stat, err
}
return f, stat, nil
}
func (s *Sync) action(event notify.EventInfo) error {
switch event.Event() {
case notify.Write:
return s.Write(event.Path())
case notify.Create:
return s.Create(event.Path())
case notify.Remove:
return s.Delete(event.Path())
case notify.Rename:
return s.Rename(event.Path())
}
return nil
}
func (s *Sync) Delete(path string) error {
if s.swapping {
s.swapping = false
_, swappedFile := s.isSwapping(path)
logger.Warning("File '%s' finished to swap, update sent.", TruncatePath(swappedFile))
return s.Write(swappedFile)
}
return s.containerFiler.Delete(s.ToRemotePath(path))
}
func (s *Sync) Write(path string) error {
if s.swapping {
return nil
}
f, stat, err := s.getFile(path)
if err != nil {
return err
}
return s.containerFiler.CopyContent(f,
stat.Size(),
s.ToRemotePath(path),
stat.Mode(),
)
}
func (s *Sync) Create(path string) error {
if s.swapping {
return nil
}
isSwapping, swappingFile := s.isSwapping(path)
if isSwapping {
s.swapping = true
logger.Warning("File '%s' is swapping, next events will be ignored.", TruncatePath(swappingFile))
return nil
}
f, stat, err := s.getFile(path)
if err != nil {
return err
}
if stat.IsDir() {
return s.containerFiler.CreateFolders(s.targetDir, s.TrimPath(path))
} else {
return s.containerFiler.CopyContent(f,
stat.Size(),
s.ToRemotePath(path),
stat.Mode(),
)
}
}
func (s *Sync) Rename(path string) error {
if s.swapping {
return nil
}
exists, err := FileExists(path)
if err != nil {
return err
}
if !exists {
isSwapping, swappingFile := s.isSwapping(path)
if isSwapping {
s.swapping = true
logger.Warning("File '%s' is swapping, next events will be ignored.", TruncatePath(swappingFile))
return nil
}
}
if s.fileToRenamed == "" && !exists {
return s.containerFiler.Delete(s.ToRemotePath(path))
}
if exists {
s.fileToRenamed = path
return nil
}
defer func() {
s.fileToRenamed = ""
}()
return s.containerFiler.Rename(s.ToRemotePath(path), s.ToRemotePath(s.fileToRenamed))
}
func (s Sync) isSwapping(path string) (isSwapping bool, pathRenamed string) {
return s.isSwappingWithLastState(path, false)
}
func (s Sync) isSwappingWithLastState(path string, state bool) (isSwapping bool, pathRenamed string) {
pathRenamed = path
ext := filepath.Ext(path)
if ext == "." {
isSwapping = false
return
}
exists, _ := FileExists(path)
if exists {
isSwapping = state
return
}
tempPath := strings.TrimSuffix(path, ext)
return s.isSwappingWithLastState(tempPath + ext[:len(ext) - 1], true)
}
func (s Sync) TrimPath(path string) string {
path = strings.TrimPrefix(path, s.sourceDir)
path = filepath.ToSlash(path)
path = strings.TrimPrefix(path, "/")
return path
}
func (s Sync) ToRemotePath(path string) string {
rmtPath := s.targetDir
if !strings.HasSuffix(rmtPath, "/") {
rmtPath += "/"
}
rmtPath = filepath.ToSlash(rmtPath)
return rmtPath + s.TrimPath(path)
}
func (s *Sync) SetForceSync(forceSync bool) {
s.forceSync = forceSync
}