Skip to content

Commit dd00cd8

Browse files
committed
fix: a lot of refactoring and unit test
1 parent 52e4074 commit dd00cd8

19 files changed

+1703
-183
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
# Output of the go coverage tool, specifically when used with LiteIDE
1212
*.out
1313

14+
# Coverage files
15+
cover.out
16+
coverage.txt
17+
1418
# Dependency directories (remove the comment below to include it)
1519
vendor/
20+
1621
.idea/
22+
23+
bin/

Makefile

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
PROJECT_NAME := "cloudwatcher"
2+
LDFLAGS="-s -w"
3+
4+
all: build-tests
5+
6+
test:
7+
@go test .
8+
9+
test-coverage:
10+
@go test -short -coverprofile cover.out -covermode=atomic .
11+
@cat cover.out >> coverage.txt
12+
13+
coverage:
14+
@go test -coverprofile=cover.out . && go tool cover -html=cover.out
15+
16+
lint:
17+
@golint -set_exit_status .
18+
19+
build-tests: clean
20+
@mkdir -p bin
21+
go build -o bin/dropbox -v -ldflags=${LDFLAGS} examples/dropbox/dropbox.go
22+
go build -o bin/gdrive -v -ldflags=${LDFLAGS} examples/gdrive/gdrive.go
23+
go build -o bin/local -v -ldflags=${LDFLAGS} examples/local/local.go
24+
go build -o bin/s3 -v -ldflags=${LDFLAGS} examples/s3/s3.go
25+
26+
clean:
27+
@rm -rf bin

booltype.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,27 @@ import (
44
"encoding/json"
55
)
66

7+
// Bool is an alias for the standard boolean type with Unmarshal/Marshal functions
78
type Bool bool
89

9-
func (bit *Bool) UnmarshalJSON(b []byte) error {
10+
// UnmarshalJSON allows to unmarshal from string to bool
11+
func (b *Bool) UnmarshalJSON(s []byte) error {
1012
var txt string
11-
err := json.Unmarshal(b, &txt)
13+
err := json.Unmarshal(s, &txt)
1214
if err != nil {
1315
return err
1416
}
15-
*bit = Bool(txt == "1" || txt == "true")
17+
if txt == "1" || txt == "true" {
18+
*b = Bool(true)
19+
} else {
20+
*b = Bool(false)
21+
}
1622
return nil
1723
}
1824

19-
func (bit *Bool) MarshalJSON() ([]byte, error) {
20-
if *bit {
25+
// MarshalJSON allows the conversion from bool to string
26+
func (b Bool) MarshalJSON() ([]byte, error) {
27+
if b {
2128
return []byte("true"), nil
2229
}
2330
return []byte("false"), nil

booltype_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package cloudwatcher
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
)
7+
8+
func TestBool_MarshalJSON(t *testing.T) {
9+
type X struct {
10+
B Bool
11+
}
12+
13+
x := X{}
14+
err := json.Unmarshal([]byte("{\"B\":\"true\"}"), &x)
15+
if err != nil {
16+
t.Errorf("error during the unmarshalling : %s", err)
17+
}
18+
19+
if x.B == false {
20+
t.Errorf("it should be true")
21+
}
22+
23+
err = json.Unmarshal([]byte("{\"B\":\"1\"}"), &x)
24+
if err != nil {
25+
t.Errorf("error during the unmarshalling : %s", err)
26+
}
27+
28+
if x.B == false {
29+
t.Errorf("it should be true")
30+
}
31+
32+
err = json.Unmarshal([]byte("{\"B\":\"false\"}"), &x)
33+
if err != nil {
34+
t.Errorf("error during the unmarshalling : %s", err)
35+
}
36+
37+
if x.B == true {
38+
t.Errorf("it should be false")
39+
}
40+
41+
err = json.Unmarshal([]byte("{\"B\":false}"), &x)
42+
if err == nil {
43+
t.Errorf("it should return an error")
44+
}
45+
}
46+
47+
func TestBool_UnmarshalJSON(t *testing.T) {
48+
type X struct {
49+
B Bool
50+
}
51+
52+
x := X{true}
53+
j, err := json.Marshal(x)
54+
if err != nil {
55+
t.Errorf("error during the marshalling : %s", err)
56+
}
57+
58+
if string(j) != "{\"B\":true}" {
59+
t.Errorf("marshalling didn't work correctly")
60+
}
61+
62+
x.B = false
63+
j, err = json.Marshal(x)
64+
if err != nil {
65+
t.Errorf("error during the marshalling : %s", err)
66+
}
67+
68+
if string(j) != "{\"B\":false}" {
69+
t.Errorf("marshalling didn't work correctly")
70+
}
71+
}

cloudwatcher.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@ import (
66
)
77

88
type storageFunc func(dir string, interval time.Duration) (Watcher, error)
9+
910
var supportedServices map[string]storageFunc
1011

12+
// WatcherBase is the struct included in all the specialized watchers
1113
type WatcherBase struct {
1214
Events chan Event
1315
Errors chan error
1416

15-
watchDir string
17+
watchDir string
1618
pollingTime time.Duration
1719
}
1820

21+
// Watcher has to be implemented by all the watchers
1922
type Watcher interface {
2023
Start() error
2124
SetConfig(c map[string]string) error
@@ -24,22 +27,26 @@ type Watcher interface {
2427
GetErrors() chan error
2528
}
2629

27-
func New(service string, dir string, interval time.Duration) (Watcher, error) {
28-
if f, ok := supportedServices[service]; !ok {
29-
return nil, fmt.Errorf("service %s is not yet supported", service)
30-
} else {
31-
return f(dir, interval)
30+
// New creates a new instance of a watcher
31+
func New(serviceName string, dir string, interval time.Duration) (Watcher, error) {
32+
f, ok := supportedServices[serviceName]
33+
if !ok {
34+
return nil, fmt.Errorf("service %s is not yet supported", serviceName)
3235
}
36+
return f(dir, interval)
37+
3338
}
3439

40+
// GetEvents returns a chan of Event
3541
func (w *WatcherBase) GetEvents() chan Event {
3642
return w.Events
3743
}
3844

45+
// GetErrors returns a chan of error
3946
func (w *WatcherBase) GetErrors() chan error {
4047
return w.Errors
4148
}
4249

4350
func init() {
4451
supportedServices = make(map[string]storageFunc)
45-
}
52+
}

dropbox.go

+40-20
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cloudwatcher
33
import (
44
"encoding/json"
55
"fmt"
6+
"path"
67
"sync/atomic"
78
"time"
89

@@ -11,28 +12,31 @@ import (
1112
"golang.org/x/oauth2"
1213
)
1314

15+
// DropboxWatcher is the specialized watcher for Dropbox service
1416
type DropboxWatcher struct {
1517
WatcherBase
1618

1719
syncing uint32
1820

1921
ticker *time.Ticker
2022
stop chan bool
21-
config *DropboxConfiguration
23+
config *dropboxConfiguration
2224
cache map[string]*DropboxObject
25+
client files.Client
2326
}
2427

28+
// DropboxObject is the object that contains the info of the file
2529
type DropboxObject struct {
2630
Key string
2731
Size int64
2832
LastModified time.Time
2933
Hash string
3034
}
3135

32-
type DropboxConfiguration struct {
36+
type dropboxConfiguration struct {
3337
Debug Bool `json:"debug"`
3438
JToken string `json:"token"`
35-
ClientId string `json:"client_id"`
39+
ClientID string `json:"client_id"`
3640
ClientSecret string `json:"client_secret"`
3741

3842
token *oauth2.Token
@@ -42,6 +46,7 @@ func newDropboxWatcher(dir string, interval time.Duration) (Watcher, error) {
4246
w := &DropboxWatcher{
4347
cache: make(map[string]*DropboxObject),
4448
config: nil,
49+
client: nil,
4550
stop: make(chan bool, 1),
4651
WatcherBase: WatcherBase{
4752
Events: make(chan Event, 100),
@@ -54,13 +59,14 @@ func newDropboxWatcher(dir string, interval time.Duration) (Watcher, error) {
5459
return w, nil
5560
}
5661

62+
// SetConfig is used to configure the DropboxWatcher
5763
func (w *DropboxWatcher) SetConfig(m map[string]string) error {
5864
j, err := json.Marshal(m)
5965
if err != nil {
6066
return err
6167
}
6268

63-
config := DropboxConfiguration{}
69+
config := dropboxConfiguration{}
6470
if err := json.Unmarshal(j, &config); err != nil {
6571
return err
6672
}
@@ -78,6 +84,7 @@ func (w *DropboxWatcher) SetConfig(m map[string]string) error {
7884
return nil
7985
}
8086

87+
// Start launches the polling process
8188
func (w *DropboxWatcher) Start() error {
8289
if w.config == nil {
8390
return fmt.Errorf("configuration for Dropbox needed")
@@ -102,8 +109,24 @@ func (w *DropboxWatcher) Start() error {
102109
return nil
103110
}
104111

112+
// Close stop the polling process
105113
func (w *DropboxWatcher) Close() {
106-
w.stop <- true
114+
if w.stop != nil {
115+
w.stop <- true
116+
}
117+
}
118+
119+
func (w *DropboxWatcher) initDropboxClient() {
120+
logLevel := dropbox.LogOff
121+
if w.config.Debug {
122+
logLevel = dropbox.LogDebug
123+
}
124+
125+
config := dropbox.Config{
126+
Token: w.config.token.AccessToken,
127+
LogLevel: logLevel,
128+
}
129+
w.client = files.New(config)
107130
}
108131

109132
func (w *DropboxWatcher) sync() {
@@ -113,8 +136,11 @@ func (w *DropboxWatcher) sync() {
113136
}
114137
defer atomic.StoreUint32(&w.syncing, 0)
115138

116-
fileList := make(map[string]*DropboxObject, 0)
139+
if w.client == nil {
140+
w.initDropboxClient()
141+
}
117142

143+
fileList := make(map[string]*DropboxObject, 0)
118144
err := w.enumerateFiles(w.watchDir, func(obj *DropboxObject) bool {
119145
// Store the files to check the deleted one
120146
fileList[obj.Key] = obj
@@ -123,7 +149,7 @@ func (w *DropboxWatcher) sync() {
123149
// Object has been cached previously by Key
124150
if cached != nil {
125151
// Check if the LastModified has been changed
126-
if !cached.LastModified.Equal(obj.LastModified) || cached.Hash != obj.Hash {
152+
if !cached.LastModified.Equal(obj.LastModified) || cached.Hash != obj.Hash || cached.Size != obj.Size {
127153
event := Event{
128154
Key: obj.Key,
129155
Type: FileChanged,
@@ -162,29 +188,19 @@ func (w *DropboxWatcher) sync() {
162188
}
163189

164190
func (w *DropboxWatcher) enumerateFiles(prefix string, callback func(object *DropboxObject) bool) error {
165-
logLevel := dropbox.LogOff
166-
if w.config.Debug {
167-
logLevel = dropbox.LogDebug
168-
}
169-
170-
config := dropbox.Config{
171-
Token: w.config.token.AccessToken,
172-
LogLevel: logLevel,
173-
}
174-
dbx := files.New(config)
175191
arg := files.NewListFolderArg(prefix)
176192
arg.Recursive = true
177193

178194
var entries []files.IsMetadata
179-
res, err := dbx.ListFolder(arg)
195+
res, err := w.client.ListFolder(arg)
180196
if err != nil {
181197
listRevisionError, ok := err.(files.ListRevisionsAPIError)
182198
if ok {
183199
// Don't treat a "not_folder" error as fatal; recover by sending a
184200
// get_metadata request for the same path and using that response instead.
185201
if listRevisionError.EndpointError.Path.Tag == files.LookupErrorNotFolder {
186202
var metaRes files.IsMetadata
187-
metaRes, err = w.getFileMetadata(dbx, prefix)
203+
metaRes, err = w.getFileMetadata(w.client, prefix)
188204
entries = []files.IsMetadata{metaRes}
189205
} else {
190206
// Return if there's an error other than "not_folder" or if the follow-up
@@ -200,7 +216,7 @@ func (w *DropboxWatcher) enumerateFiles(prefix string, callback func(object *Dro
200216
for res.HasMore {
201217
arg := files.NewListFolderContinueArg(res.Cursor)
202218

203-
res, err = dbx.ListFolderContinue(arg)
219+
res, err = w.client.ListFolderContinue(arg)
204220
if err != nil {
205221
return err
206222
}
@@ -214,10 +230,14 @@ func (w *DropboxWatcher) enumerateFiles(prefix string, callback func(object *Dro
214230
switch f := entry.(type) {
215231
case *files.FileMetadata:
216232
o.Key = f.PathDisplay
233+
if f.PathDisplay == "" {
234+
o.Key = path.Join(f.PathLower, f.Name)
235+
}
217236
o.Size = int64(f.Size)
218237
o.LastModified = f.ServerModified
219238
o.Hash = f.ContentHash
220239
callback(o)
240+
default:
221241
}
222242
}
223243

0 commit comments

Comments
 (0)