Skip to content

Commit 5be744a

Browse files
committed
new: dropbox support added
1 parent eb34958 commit 5be744a

5 files changed

Lines changed: 390 additions & 8 deletions

File tree

booltype.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cloudwatcher
2+
3+
type Bool bool
4+
5+
func (bit *Bool) UnmarshalJSON(b []byte) error {
6+
txt := string(b)
7+
*bit = Bool(txt == "1" || txt == "true")
8+
return nil
9+
}
10+
11+
func (bit *Bool) MarshalJSON() ([]byte, error) {
12+
if *bit {
13+
return []byte("true"), nil
14+
}
15+
return []byte("false"), nil
16+
}

dropbox.go

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
package cloudwatcher
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"sync/atomic"
7+
"time"
8+
9+
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
10+
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files"
11+
"github.com/minio/minio-go"
12+
)
13+
14+
type DropboxWatcher struct {
15+
WatcherBase
16+
17+
syncing uint32
18+
19+
ticker *time.Ticker
20+
stop chan bool
21+
config *DropboxConfiguration
22+
client *minio.Client
23+
cache map[string]*DropboxObject
24+
}
25+
26+
type DropboxObject struct {
27+
Key string
28+
Size int64
29+
LastModified time.Time
30+
Hash string
31+
}
32+
33+
type DropboxConfiguration struct {
34+
Debug Bool `json:"debug"`
35+
Token string `json:"token"`
36+
}
37+
38+
func newDropboxWatcher(dir string, interval time.Duration) (Watcher, error) {
39+
w := &DropboxWatcher{
40+
cache: make(map[string]*DropboxObject),
41+
config: nil,
42+
stop: make(chan bool, 1),
43+
WatcherBase: WatcherBase{
44+
Events: make(chan Event, 100),
45+
Errors: make(chan error, 100),
46+
watchDir: dir,
47+
pollingTime: interval,
48+
},
49+
}
50+
51+
return w, nil
52+
}
53+
54+
func (w *DropboxWatcher) SetConfig(m map[string]string) error {
55+
j, err := json.Marshal(m)
56+
if err != nil {
57+
return err
58+
}
59+
60+
config := DropboxConfiguration{}
61+
if err := json.Unmarshal(j, &config); err != nil {
62+
return err
63+
}
64+
65+
if config.Token == "" {
66+
return fmt.Errorf("token not specified")
67+
}
68+
w.config = &config
69+
70+
71+
fmt.Printf("%v", config)
72+
return nil
73+
}
74+
75+
func (w *DropboxWatcher) Start() error {
76+
if w.config == nil {
77+
return fmt.Errorf("configuration for Dropbox needed")
78+
}
79+
80+
w.ticker = time.NewTicker(w.pollingTime)
81+
go func() {
82+
// launch synchronization also the first time
83+
w.sync()
84+
for {
85+
select {
86+
case <-w.ticker.C:
87+
w.sync()
88+
89+
case <-w.stop:
90+
close(w.Events)
91+
close(w.Errors)
92+
return
93+
}
94+
}
95+
}()
96+
return nil
97+
}
98+
99+
func (w *DropboxWatcher) Close() {
100+
w.stop <- true
101+
}
102+
103+
func (w *DropboxWatcher) sync() {
104+
// allow only one sync at same time
105+
if !atomic.CompareAndSwapUint32(&w.syncing, 0, 1) {
106+
return
107+
}
108+
defer atomic.StoreUint32(&w.syncing, 0)
109+
110+
fileList := make(map[string]*DropboxObject, 0)
111+
112+
err := w.enumerateFiles(w.watchDir, func(obj *DropboxObject) bool {
113+
// Store the files to check the deleted one
114+
fileList[obj.Key] = obj
115+
// Check if the object is cached by Key
116+
cached := w.getCachedObject(obj)
117+
// Object has been cached previously by Key
118+
if cached != nil {
119+
// Check if the LastModified has been changed
120+
if !cached.LastModified.Equal(obj.LastModified) || cached.Hash != obj.Hash {
121+
event := Event{
122+
Key: obj.Key,
123+
Type: FileChanged,
124+
Object: obj,
125+
}
126+
w.Events <- event
127+
}
128+
} else {
129+
event := Event{
130+
Key: obj.Key,
131+
Type: FileCreated,
132+
Object: obj,
133+
}
134+
w.Events <- event
135+
}
136+
w.cache[obj.Key] = obj
137+
return true
138+
})
139+
if err != nil {
140+
w.Errors <- err
141+
return
142+
}
143+
144+
for k, o := range w.cache {
145+
if _, found := fileList[k]; !found {
146+
// file not found in the list...deleting it
147+
delete(w.cache, k)
148+
event := Event{
149+
Key: o.Key,
150+
Type: FileDeleted,
151+
Object: nil,
152+
}
153+
w.Events <- event
154+
}
155+
}
156+
}
157+
158+
func (w *DropboxWatcher) enumerateFiles(prefix string, callback func(object *DropboxObject) bool) error {
159+
logLevel := dropbox.LogOff
160+
if w.config.Debug {
161+
logLevel = dropbox.LogDebug
162+
}
163+
164+
config := dropbox.Config{
165+
Token: w.config.Token,
166+
LogLevel: logLevel,
167+
Logger: nil,
168+
AsMemberID: "",
169+
Domain: "",
170+
Client: nil,
171+
HeaderGenerator: nil,
172+
URLGenerator: nil,
173+
}
174+
dbx := files.New(config)
175+
arg := files.NewListFolderArg(prefix)
176+
arg.Recursive = true
177+
178+
var entries []files.IsMetadata
179+
res, err := dbx.ListFolder(arg)
180+
if err != nil {
181+
listRevisionError, ok := err.(files.ListRevisionsAPIError)
182+
if ok {
183+
// Don't treat a "not_folder" error as fatal; recover by sending a
184+
// get_metadata request for the same path and using that response instead.
185+
if listRevisionError.EndpointError.Path.Tag == files.LookupErrorNotFolder {
186+
var metaRes files.IsMetadata
187+
metaRes, err = w.getFileMetadata(dbx, prefix)
188+
entries = []files.IsMetadata{metaRes}
189+
} else {
190+
// Return if there's an error other than "not_folder" or if the follow-up
191+
// metadata request fails.
192+
return err
193+
}
194+
} else {
195+
return err
196+
}
197+
} else {
198+
entries = res.Entries
199+
200+
for res.HasMore {
201+
arg := files.NewListFolderContinueArg(res.Cursor)
202+
203+
res, err = dbx.ListFolderContinue(arg)
204+
if err != nil {
205+
return err
206+
}
207+
208+
entries = append(entries, res.Entries...)
209+
}
210+
}
211+
212+
for _, entry := range entries {
213+
o := &DropboxObject{}
214+
switch f := entry.(type) {
215+
case *files.FileMetadata:
216+
o.Key = f.PathDisplay
217+
o.Size = int64(f.Size)
218+
o.LastModified = f.ServerModified
219+
o.Hash = f.ContentHash
220+
callback(o)
221+
//case *files.FolderMetadata:
222+
// o.Key = f.PathDisplay
223+
// o.Size = 0
224+
// o.LastModified = 0
225+
}
226+
}
227+
228+
return nil
229+
}
230+
231+
func (w *DropboxWatcher) getFileMetadata(c files.Client, path string) (files.IsMetadata, error) {
232+
arg := files.NewGetMetadataArg(path)
233+
234+
arg.IncludeDeleted = true
235+
236+
res, err := c.GetMetadata(arg)
237+
if err != nil {
238+
return nil, err
239+
}
240+
241+
return res, nil
242+
}
243+
244+
func (w *DropboxWatcher) getCachedObject(o *DropboxObject) *DropboxObject {
245+
if cachedObject, ok := w.cache[o.Key]; ok {
246+
return cachedObject
247+
}
248+
return nil
249+
}
250+
251+
func init() {
252+
supportedServices["dropbox"] = newDropboxWatcher
253+
}

examples/dropbox.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/Matrix86/cloudwatcher"
7+
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
8+
"golang.org/x/oauth2"
9+
"time"
10+
)
11+
12+
func getAuthCode() (string, error) {
13+
conf := &oauth2.Config{
14+
ClientID: "CLIENT_ID_HERE",
15+
ClientSecret: "CLIENT_SECRET_HERE",
16+
Endpoint: dropbox.OAuthEndpoint(""),
17+
}
18+
19+
fmt.Printf("1. Go to %v\n", conf.AuthCodeURL("state"))
20+
fmt.Printf("2. Click \"Allow\" (you might have to log in first).\n")
21+
fmt.Printf("3. Copy the authorization code.\n")
22+
fmt.Printf("Enter the authorization code here: ")
23+
24+
var code string
25+
if _, err := fmt.Scan(&code); err != nil {
26+
return "", err
27+
}
28+
29+
ctx := context.Background()
30+
token, err := conf.Exchange(ctx, code)
31+
if err != nil {
32+
return "", err
33+
}
34+
return token.AccessToken, nil
35+
}
36+
func main() {
37+
s, err := cloudwatcher.New("dropbox", "", 2*time.Second)
38+
if err != nil {
39+
fmt.Printf("ERROR: %s", err)
40+
return
41+
}
42+
43+
config := map[string]string{
44+
"debug": "true",
45+
"token": "",
46+
}
47+
48+
if v, ok := config["token"]; !ok || v == "" {
49+
token, err := getAuthCode()
50+
if err != nil {
51+
fmt.Printf("ERROR: %s", err)
52+
return
53+
}
54+
fmt.Printf("NEW TOKEN: %s\n", token)
55+
config["token"] = token
56+
}
57+
58+
err = s.SetConfig(config)
59+
if err != nil {
60+
fmt.Printf("ERROR: %s", err)
61+
return
62+
}
63+
64+
err = s.Start()
65+
defer s.Close()
66+
for {
67+
select {
68+
case v := <-s.GetEvents():
69+
fmt.Printf("EVENT: %s %s\n", v.Key, v.TypeString())
70+
71+
case e := <-s.GetErrors():
72+
fmt.Printf("ERROR: %s\n", e)
73+
}
74+
}
75+
}

misc.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package cloudwatcher
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strings"
7+
)
8+
9+
func convertStructToMap(st interface{}) map[string]string {
10+
11+
reqRules := make(map[string]string)
12+
13+
v := reflect.ValueOf(st)
14+
t := reflect.TypeOf(st)
15+
16+
if t.Kind() == reflect.Ptr {
17+
v = v.Elem()
18+
}
19+
20+
for i := 0; i < v.NumField(); i++ {
21+
key := strings.ToLower(t.Field(i).Name)
22+
typ := v.FieldByName(t.Field(i).Name).Kind().String()
23+
structTag := t.Field(i).Tag.Get("json")
24+
jsonName := strings.TrimSpace(strings.Split(structTag, ",")[0])
25+
value := v.FieldByName(t.Field(i).Name)
26+
27+
// if jsonName is not empty use it for the key
28+
if jsonName != "" && jsonName != "-" {
29+
key = jsonName
30+
}
31+
32+
if typ == "string" {
33+
reqRules[key] = value.String()
34+
} else if typ == "int" {
35+
reqRules[key] = fmt.Sprintf("%d", value.Int())
36+
} else if typ == "bool" {
37+
reqRules[key] = fmt.Sprintf("%t", value.Bool())
38+
} else {
39+
reqRules[key] = ""
40+
}
41+
42+
}
43+
44+
return reqRules
45+
}

0 commit comments

Comments
 (0)