1package main 2 3import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "net/http" 8 "os" 9 "path/filepath" 10 "time" 11 12 "github.com/prasmussen/gdrive/auth" 13 "github.com/prasmussen/gdrive/cli" 14 "github.com/prasmussen/gdrive/drive" 15) 16 17const ClientId = "367116221053-7n0vf5akeru7on6o2fjinrecpdoe99eg.apps.googleusercontent.com" 18const ClientSecret = "1qsNodXNaWq1mQuBjUjmvhoO" 19const TokenFilename = "token_v2.json" 20const ClientCredentialsFilename = "client_id.json" 21const DefaultCacheFileName = "file_cache.json" 22 23var usingClientCredentialsFile = false 24 25func listHandler(ctx cli.Context) { 26 args := ctx.Args() 27 err := newDrive(args).List(drive.ListFilesArgs{ 28 Out: os.Stdout, 29 MaxFiles: args.Int64("maxFiles"), 30 NameWidth: args.Int64("nameWidth"), 31 Query: args.String("query"), 32 SortOrder: args.String("sortOrder"), 33 SkipHeader: args.Bool("skipHeader"), 34 SizeInBytes: args.Bool("sizeInBytes"), 35 AbsPath: args.Bool("absPath"), 36 }) 37 checkErr(err) 38} 39 40func listChangesHandler(ctx cli.Context) { 41 args := ctx.Args() 42 err := newDrive(args).ListChanges(drive.ListChangesArgs{ 43 Out: os.Stdout, 44 PageToken: args.String("pageToken"), 45 MaxChanges: args.Int64("maxChanges"), 46 Now: args.Bool("now"), 47 NameWidth: args.Int64("nameWidth"), 48 SkipHeader: args.Bool("skipHeader"), 49 }) 50 checkErr(err) 51} 52 53func downloadHandler(ctx cli.Context) { 54 args := ctx.Args() 55 checkDownloadArgs(args) 56 err := newDrive(args).Download(drive.DownloadArgs{ 57 Out: os.Stdout, 58 Id: args.String("fileId"), 59 Force: args.Bool("force"), 60 Skip: args.Bool("skip"), 61 Path: args.String("path"), 62 Delete: args.Bool("delete"), 63 Recursive: args.Bool("recursive"), 64 Stdout: args.Bool("stdout"), 65 Progress: progressWriter(args.Bool("noProgress")), 66 Timeout: durationInSeconds(args.Int64("timeout")), 67 }) 68 checkErr(err) 69} 70 71func downloadQueryHandler(ctx cli.Context) { 72 args := ctx.Args() 73 err := newDrive(args).DownloadQuery(drive.DownloadQueryArgs{ 74 Out: os.Stdout, 75 Query: args.String("query"), 76 Force: args.Bool("force"), 77 Skip: args.Bool("skip"), 78 Recursive: args.Bool("recursive"), 79 Path: args.String("path"), 80 Progress: progressWriter(args.Bool("noProgress")), 81 }) 82 checkErr(err) 83} 84 85func downloadSyncHandler(ctx cli.Context) { 86 args := ctx.Args() 87 cachePath := filepath.Join(args.String("configDir"), DefaultCacheFileName) 88 err := newDrive(args).DownloadSync(drive.DownloadSyncArgs{ 89 Out: os.Stdout, 90 Progress: progressWriter(args.Bool("noProgress")), 91 Path: args.String("path"), 92 RootId: args.String("fileId"), 93 DryRun: args.Bool("dryRun"), 94 DeleteExtraneous: args.Bool("deleteExtraneous"), 95 Timeout: durationInSeconds(args.Int64("timeout")), 96 Resolution: conflictResolution(args), 97 Comparer: NewCachedMd5Comparer(cachePath), 98 }) 99 checkErr(err) 100} 101 102func downloadRevisionHandler(ctx cli.Context) { 103 args := ctx.Args() 104 err := newDrive(args).DownloadRevision(drive.DownloadRevisionArgs{ 105 Out: os.Stdout, 106 FileId: args.String("fileId"), 107 RevisionId: args.String("revId"), 108 Force: args.Bool("force"), 109 Stdout: args.Bool("stdout"), 110 Path: args.String("path"), 111 Progress: progressWriter(args.Bool("noProgress")), 112 Timeout: durationInSeconds(args.Int64("timeout")), 113 }) 114 checkErr(err) 115} 116 117func uploadHandler(ctx cli.Context) { 118 args := ctx.Args() 119 checkUploadArgs(args) 120 err := newDrive(args).Upload(drive.UploadArgs{ 121 Out: os.Stdout, 122 Progress: progressWriter(args.Bool("noProgress")), 123 Path: args.String("path"), 124 Name: args.String("name"), 125 Description: args.String("description"), 126 Parents: args.StringSlice("parent"), 127 Mime: args.String("mime"), 128 Recursive: args.Bool("recursive"), 129 Share: args.Bool("share"), 130 Delete: args.Bool("delete"), 131 ChunkSize: args.Int64("chunksize"), 132 Timeout: durationInSeconds(args.Int64("timeout")), 133 }) 134 checkErr(err) 135} 136 137func uploadStdinHandler(ctx cli.Context) { 138 args := ctx.Args() 139 err := newDrive(args).UploadStream(drive.UploadStreamArgs{ 140 Out: os.Stdout, 141 In: os.Stdin, 142 Name: args.String("name"), 143 Description: args.String("description"), 144 Parents: args.StringSlice("parent"), 145 Mime: args.String("mime"), 146 Share: args.Bool("share"), 147 ChunkSize: args.Int64("chunksize"), 148 Timeout: durationInSeconds(args.Int64("timeout")), 149 Progress: progressWriter(args.Bool("noProgress")), 150 }) 151 checkErr(err) 152} 153 154func uploadSyncHandler(ctx cli.Context) { 155 args := ctx.Args() 156 cachePath := filepath.Join(args.String("configDir"), DefaultCacheFileName) 157 err := newDrive(args).UploadSync(drive.UploadSyncArgs{ 158 Out: os.Stdout, 159 Progress: progressWriter(args.Bool("noProgress")), 160 Path: args.String("path"), 161 RootId: args.String("fileId"), 162 DryRun: args.Bool("dryRun"), 163 DeleteExtraneous: args.Bool("deleteExtraneous"), 164 ChunkSize: args.Int64("chunksize"), 165 Timeout: durationInSeconds(args.Int64("timeout")), 166 Resolution: conflictResolution(args), 167 Comparer: NewCachedMd5Comparer(cachePath), 168 }) 169 checkErr(err) 170} 171 172func updateHandler(ctx cli.Context) { 173 args := ctx.Args() 174 err := newDrive(args).Update(drive.UpdateArgs{ 175 Out: os.Stdout, 176 Id: args.String("fileId"), 177 Path: args.String("path"), 178 Name: args.String("name"), 179 Description: args.String("description"), 180 Parents: args.StringSlice("parent"), 181 Mime: args.String("mime"), 182 Progress: progressWriter(args.Bool("noProgress")), 183 ChunkSize: args.Int64("chunksize"), 184 Timeout: durationInSeconds(args.Int64("timeout")), 185 }) 186 checkErr(err) 187} 188 189func infoHandler(ctx cli.Context) { 190 args := ctx.Args() 191 err := newDrive(args).Info(drive.FileInfoArgs{ 192 Out: os.Stdout, 193 Id: args.String("fileId"), 194 SizeInBytes: args.Bool("sizeInBytes"), 195 }) 196 checkErr(err) 197} 198 199func importHandler(ctx cli.Context) { 200 args := ctx.Args() 201 err := newDrive(args).Import(drive.ImportArgs{ 202 Mime: args.String("mime"), 203 Out: os.Stdout, 204 Path: args.String("path"), 205 Parents: args.StringSlice("parent"), 206 Progress: progressWriter(args.Bool("noProgress")), 207 }) 208 checkErr(err) 209} 210 211func exportHandler(ctx cli.Context) { 212 args := ctx.Args() 213 err := newDrive(args).Export(drive.ExportArgs{ 214 Out: os.Stdout, 215 Id: args.String("fileId"), 216 Mime: args.String("mime"), 217 PrintMimes: args.Bool("printMimes"), 218 Force: args.Bool("force"), 219 }) 220 checkErr(err) 221} 222 223func listRevisionsHandler(ctx cli.Context) { 224 args := ctx.Args() 225 err := newDrive(args).ListRevisions(drive.ListRevisionsArgs{ 226 Out: os.Stdout, 227 Id: args.String("fileId"), 228 NameWidth: args.Int64("nameWidth"), 229 SizeInBytes: args.Bool("sizeInBytes"), 230 SkipHeader: args.Bool("skipHeader"), 231 }) 232 checkErr(err) 233} 234 235func mkdirHandler(ctx cli.Context) { 236 args := ctx.Args() 237 err := newDrive(args).Mkdir(drive.MkdirArgs{ 238 Out: os.Stdout, 239 Name: args.String("name"), 240 Description: args.String("description"), 241 Parents: args.StringSlice("parent"), 242 }) 243 checkErr(err) 244} 245 246func shareHandler(ctx cli.Context) { 247 args := ctx.Args() 248 err := newDrive(args).Share(drive.ShareArgs{ 249 Out: os.Stdout, 250 FileId: args.String("fileId"), 251 Role: args.String("role"), 252 Type: args.String("type"), 253 Email: args.String("email"), 254 Domain: args.String("domain"), 255 Discoverable: args.Bool("discoverable"), 256 }) 257 checkErr(err) 258} 259 260func shareListHandler(ctx cli.Context) { 261 args := ctx.Args() 262 err := newDrive(args).ListPermissions(drive.ListPermissionsArgs{ 263 Out: os.Stdout, 264 FileId: args.String("fileId"), 265 }) 266 checkErr(err) 267} 268 269func shareRevokeHandler(ctx cli.Context) { 270 args := ctx.Args() 271 err := newDrive(args).RevokePermission(drive.RevokePermissionArgs{ 272 Out: os.Stdout, 273 FileId: args.String("fileId"), 274 PermissionId: args.String("permissionId"), 275 }) 276 checkErr(err) 277} 278 279func deleteHandler(ctx cli.Context) { 280 args := ctx.Args() 281 err := newDrive(args).Delete(drive.DeleteArgs{ 282 Out: os.Stdout, 283 Id: args.String("fileId"), 284 Recursive: args.Bool("recursive"), 285 }) 286 checkErr(err) 287} 288 289func listSyncHandler(ctx cli.Context) { 290 args := ctx.Args() 291 err := newDrive(args).ListSync(drive.ListSyncArgs{ 292 Out: os.Stdout, 293 SkipHeader: args.Bool("skipHeader"), 294 }) 295 checkErr(err) 296} 297 298func listRecursiveSyncHandler(ctx cli.Context) { 299 args := ctx.Args() 300 err := newDrive(args).ListRecursiveSync(drive.ListRecursiveSyncArgs{ 301 Out: os.Stdout, 302 RootId: args.String("fileId"), 303 SkipHeader: args.Bool("skipHeader"), 304 PathWidth: args.Int64("pathWidth"), 305 SizeInBytes: args.Bool("sizeInBytes"), 306 SortOrder: args.String("sortOrder"), 307 }) 308 checkErr(err) 309} 310 311func deleteRevisionHandler(ctx cli.Context) { 312 args := ctx.Args() 313 err := newDrive(args).DeleteRevision(drive.DeleteRevisionArgs{ 314 Out: os.Stdout, 315 FileId: args.String("fileId"), 316 RevisionId: args.String("revId"), 317 }) 318 checkErr(err) 319} 320 321func aboutHandler(ctx cli.Context) { 322 args := ctx.Args() 323 err := newDrive(args).About(drive.AboutArgs{ 324 Out: os.Stdout, 325 SizeInBytes: args.Bool("sizeInBytes"), 326 }) 327 checkErr(err) 328} 329 330func aboutImportHandler(ctx cli.Context) { 331 args := ctx.Args() 332 err := newDrive(args).AboutImport(drive.AboutImportArgs{ 333 Out: os.Stdout, 334 }) 335 checkErr(err) 336} 337 338func aboutExportHandler(ctx cli.Context) { 339 args := ctx.Args() 340 err := newDrive(args).AboutExport(drive.AboutExportArgs{ 341 Out: os.Stdout, 342 }) 343 checkErr(err) 344} 345 346func getOauthClient(args cli.Arguments) (*http.Client, error) { 347 if args.String("refreshToken") != "" && args.String("accessToken") != "" { 348 ExitF("Access token not needed when refresh token is provided") 349 } 350 351 configDir := getConfigDir(args) 352 353 clientCredentialsPath := ConfigFilePath(configDir, ClientCredentialsFilename) 354 clientCredentials, exists, err := auth.ReadClientCredentials(clientCredentialsPath) 355 if err != nil { 356 ExitF("Failed to read client credentials file: %s", err) 357 } else if !exists { 358 clientCredentials = auth.AssembleClientCredentials(ClientId, ClientSecret) 359 } else { 360 usingClientCredentialsFile = true 361 // Make sure the google drive scope is present 362 if len(clientCredentials.Scopes) == 0 { 363 clientCredentials.Scopes = append(clientCredentials.Scopes, "https://www.googleapis.com/auth/drive") 364 } 365 } 366 367 368 if args.String("refreshToken") != "" { 369 return auth.NewRefreshTokenClient(clientCredentials, args.String("refreshToken")), nil 370 } 371 372 if args.String("accessToken") != "" { 373 return auth.NewAccessTokenClient(clientCredentials, args.String("accessToken")), nil 374 } 375 376 if args.String("serviceAccount") != "" { 377 serviceAccountPath := ConfigFilePath(configDir, args.String("serviceAccount")) 378 serviceAccountClient, err := auth.NewServiceAccountClient(serviceAccountPath) 379 if err != nil { 380 return nil, err 381 } 382 return serviceAccountClient, nil 383 } 384 385 tokenPath := ConfigFilePath(configDir, TokenFilename) 386 return auth.NewFileSourceClient(clientCredentials, tokenPath, authCodePrompt) 387} 388 389func getConfigDir(args cli.Arguments) string { 390 // Use dir from environment var if present 391 if os.Getenv("GDRIVE_CONFIG_DIR") != "" { 392 return os.Getenv("GDRIVE_CONFIG_DIR") 393 } 394 return args.String("configDir") 395} 396 397func newDrive(args cli.Arguments) *drive.Drive { 398 oauth, err := getOauthClient(args) 399 if err != nil { 400 ExitF("Failed getting oauth client: %s", err.Error()) 401 } 402 403 client, err := drive.New(oauth) 404 if err != nil { 405 ExitF("Failed getting drive: %s", err.Error()) 406 } 407 408 return client 409} 410 411func authCodePrompt(url string) func() string { 412 return func() string { 413 if (usingClientCredentialsFile == true) { 414 fmt.Println("Client credentials loaded from file\n") 415 } else { 416 fmt.Println("Client credentials file not found. Using built-in defaults\n") 417 } 418 419 fmt.Println("Authentication needed") 420 fmt.Println("Go to the following url in your browser:") 421 fmt.Printf("%s\n\n", url) 422 fmt.Print("Enter verification code: ") 423 424 var code string 425 if _, err := fmt.Scan(&code); err != nil { 426 fmt.Printf("Failed reading code: %s", err.Error()) 427 } 428 return code 429 } 430} 431 432func progressWriter(discard bool) io.Writer { 433 if discard { 434 return ioutil.Discard 435 } 436 return os.Stderr 437} 438 439func durationInSeconds(seconds int64) time.Duration { 440 return time.Second * time.Duration(seconds) 441} 442 443func conflictResolution(args cli.Arguments) drive.ConflictResolution { 444 keepLocal := args.Bool("keepLocal") 445 keepRemote := args.Bool("keepRemote") 446 keepLargest := args.Bool("keepLargest") 447 448 if (keepLocal && keepRemote) || (keepLocal && keepLargest) || (keepRemote && keepLargest) { 449 ExitF("Only one conflict resolution flag can be given") 450 } 451 452 if keepLocal { 453 return drive.KeepLocal 454 } 455 456 if keepRemote { 457 return drive.KeepRemote 458 } 459 460 if keepLargest { 461 return drive.KeepLargest 462 } 463 464 return drive.NoResolution 465} 466 467func checkUploadArgs(args cli.Arguments) { 468 if args.Bool("recursive") && args.Bool("delete") { 469 ExitF("--delete is not allowed for recursive uploads") 470 } 471 472 if args.Bool("recursive") && args.Bool("share") { 473 ExitF("--share is not allowed for recursive uploads") 474 } 475} 476 477func checkDownloadArgs(args cli.Arguments) { 478 if args.Bool("recursive") && args.Bool("delete") { 479 ExitF("--delete is not allowed for recursive downloads") 480 } 481} 482