-
Notifications
You must be signed in to change notification settings - Fork 29
/
main.go
executable file
·306 lines (257 loc) · 8.1 KB
/
main.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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
package main
import (
"bufio"
"flag"
"fmt"
"log"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/simonedegiacomi/gphotosuploader/api"
"github.com/simonedegiacomi/gphotosuploader/utils"
"github.com/fsnotify/fsnotify"
"github.com/simonedegiacomi/gphotosuploader/auth"
"github.com/simonedegiacomi/gphotosuploader/version"
)
var (
// CLI arguments
authFile string
filesToUpload utils.FilesToUpload
directoriesToWatch utils.DirectoriesToWatch
albumId string
albumName string
uploadedListFile string
watchRecursively bool
maxConcurrentUploads int
eventDelay time.Duration
printVersion bool
// Uploader
uploader *utils.ConcurrentUploader
timers = make(map[string]*time.Timer)
// Statistics
uploadedFilesCount = 0
ignoredCount = 0
errorsCount = 0
)
func main() {
parseCliArguments()
if printVersion {
fmt.Printf("Hash:\t%s\nCommit date:\t%s\n", version.Hash, version.Date)
os.Exit(0)
}
credentials := initAuthentication()
var err error
// Create Album first to get albumId
if albumName != "" {
albumId, err = api.CreateAlbum(credentials, albumName)
if err != nil {
log.Fatalf("Can't create album: %v\n", err)
}
log.Printf("New album with ID '%v' created\n", albumId)
}
uploader, err = utils.NewUploader(credentials, albumId, maxConcurrentUploads)
if err != nil {
log.Fatalf("Can't create uploader: %v\n", err)
}
stopHandler := make(chan bool)
go handleUploaderEvents(stopHandler)
loadAlreadyUploadedFiles()
// Upload files passed as arguments
uploadArgumentsFiles()
// Wait until all the uploads are completed
uploader.WaitUploadsCompleted()
// Start to watch all the directories if needed
if len(directoriesToWatch) > 0 {
watcher, err := fsnotify.NewWatcher()
if err != nil {
panic(err)
}
defer watcher.Close()
go handleFileSystemEvents(watcher, stopHandler)
// Add all the directories passed as argument to the watcher
for _, name := range directoriesToWatch {
if err := startToWatch(name, watcher); err != nil {
panic(err)
}
}
log.Println("Watching 👀\nPress CTRL + C to stop")
// Wait for CTRL + C
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
}
stopHandler <- true
<-stopHandler
stopHandler <- true
<-stopHandler
log.Printf("Done (%v files uploaded, %v files ignored, %v errors)", uploadedFilesCount, ignoredCount, errorsCount)
os.Exit(0)
}
// Parse CLI arguments
func parseCliArguments() {
flag.StringVar(&authFile, "auth", "auth.json", "Authentication json file")
flag.Var(&filesToUpload, "upload", "File or directory to upload")
flag.StringVar(&albumId, "album", "", "Use this parameter to move new images to a specific album")
flag.StringVar(&albumName, "albumName", "", "Use this parameter to move new images to a new album")
flag.StringVar(&uploadedListFile, "uploadedList", "uploaded.txt", "List to already uploaded files")
flag.IntVar(&maxConcurrentUploads, "maxConcurrent", 1, "Number of max concurrent uploads")
flag.Var(&directoriesToWatch, "watch", "Directory to watch")
flag.BoolVar(&watchRecursively, "watchRecursively", true, "Start watching new directories in currently watched directories")
delay := flag.Int("eventDelay", 3, "Distance of time to wait to consume different events of the same file (seconds)")
flag.BoolVar(&printVersion, "version", false, "Print version and commit date")
flag.Parse()
// Check flags
if albumId != "" && albumName != "" {
log.Fatalf("Can't use album and albumName at the same time\n")
}
// Convert delay as int into duration
eventDelay = time.Duration(*delay) * time.Second
}
func initAuthentication() auth.CookieCredentials {
// Load authentication parameters
credentials, err := auth.NewCookieCredentialsFromFile(authFile)
if err != nil {
log.Printf("Can't use '%v' as auth file\n", authFile)
credentials = nil
} else {
log.Println("Auth file loaded, checking validity ...")
validity, err := credentials.CheckCredentials()
if err != nil {
log.Fatalf("Can't check validity of credentials (%v)\n", err)
credentials = nil
} else if !validity.Valid {
log.Printf("Credentials are not valid! %v\n", validity.Reason)
credentials = nil
} else {
log.Println("Auth file seems to be valid")
}
}
if credentials == nil {
fmt.Println("The uploader can't continue without valid authentication tokens ...")
fmt.Println("Would you like to run the WebDriver CookieCredentials Wizard ? [Yes/No]")
fmt.Println("(If you don't know what it is, refer to the README)")
var answer string
fmt.Scanln(&answer)
startWizard := len(answer) > 0 && strings.ToLower(answer)[0] == 'y'
if !startWizard {
log.Fatalln("It's not possible to continue, sorry!")
} else {
credentials, err = utils.StartWebDriverCookieCredentialsWizard()
if err != nil {
log.Fatalf("Can't complete the login wizard, got: %v\n", err)
} else {
// TODO: Handle error
credentials.SerializeToFile(authFile)
}
}
}
// Get a new At token
log.Println("Getting a new At token ...")
token, err := api.NewAtTokenScraper(*credentials).ScrapeNewAtToken()
if err != nil {
log.Fatalf("Can't scrape a new At token (%v)\n", err)
}
credentials.RuntimeParameters.AtToken = token
log.Println("At token taken")
return *credentials
}
// Upload all the file and directories passed as arguments, calling filepath.Walk on each name
func uploadArgumentsFiles() {
for _, name := range filesToUpload {
filepath.Walk(name, func(path string, file os.FileInfo, err error) error {
if !file.IsDir() {
uploader.EnqueueUpload(path)
}
return nil
})
}
}
func handleUploaderEvents(exiting chan bool) {
for {
select {
case info := <-uploader.CompletedUploads:
uploadedFilesCount++
log.Printf("Upload of '%v' completed\n", info)
// Update the upload completed file
if file, err := os.OpenFile(uploadedListFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666); err != nil {
log.Println("Can't update the uploaded file list")
} else {
file.WriteString(info + "\n")
file.Close()
}
case info := <-uploader.IgnoredUploads:
ignoredCount++
log.Printf("Not uploading '%v', it's already been uploaded or it's not a image/video!\n", info)
case err := <-uploader.Errors:
log.Printf("Upload error: %v\n", err)
errorsCount++
case <-exiting:
exiting <- true
break
}
}
}
func startToWatch(filePath string, fsWatcher *fsnotify.Watcher) error {
if watchRecursively {
return filepath.Walk(filePath, func(path string, file os.FileInfo, err error) error {
if file.IsDir() {
return fsWatcher.Add(path)
}
return nil
})
} else {
return fsWatcher.Add(filePath)
}
}
func handleFileChange(event fsnotify.Event, fsWatcher *fsnotify.Watcher) {
// Use a map of timer to ignore different consecutive events for the same file.
// (when the os writes a file to the disk, sometimes it repetitively sends same events)
if timer, exists := timers[event.Name]; exists {
// Cancel the timer
cancelled := timer.Stop()
if cancelled && event.Op != fsnotify.Remove && event.Op != fsnotify.Rename {
// Postpone the file upload
timer.Reset(eventDelay)
}
} else if event.Op != fsnotify.Remove && event.Op != fsnotify.Rename {
timer = time.AfterFunc(eventDelay, func() {
log.Printf("Finally consuming events for the %v file", event.Name)
if info, err := os.Stat(event.Name); err != nil {
log.Println(err)
} else if !info.IsDir() {
// Upload file
uploader.EnqueueUpload(event.Name)
} else if watchRecursively {
startToWatch(event.Name, fsWatcher)
}
})
timers[event.Name] = timer
}
}
func handleFileSystemEvents(fsWatcher *fsnotify.Watcher, exiting chan bool) {
for {
select {
case event := <-fsWatcher.Events:
handleFileChange(event, fsWatcher)
case err := <-fsWatcher.Errors:
log.Println(err)
case <-exiting:
exiting <- true
return
}
}
}
func loadAlreadyUploadedFiles() {
file, err := os.OpenFile(uploadedListFile, os.O_CREATE, 0666)
if err != nil {
panic(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
uploader.AddUploadedFiles(scanner.Text())
}
}