-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
922f57d
commit 825660c
Showing
8 changed files
with
759 additions
and
687 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package auth | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"os/user" | ||
"path/filepath" | ||
|
||
"golang.org/x/net/context" | ||
"golang.org/x/oauth2" | ||
"golang.org/x/oauth2/google" | ||
"google.golang.org/api/drive/v3" | ||
) | ||
|
||
// getClient uses a Context and Config to retrieve a Token | ||
// then generate a Client. It returns the generated Client. | ||
func getClient(ctx context.Context, config *oauth2.Config) *http.Client { | ||
cacheFile, err := tokenCacheFile() | ||
if err != nil { | ||
log.Fatalf("Unable to get path to cached credential file: %v", err) | ||
} | ||
tok, err := tokenFromFile(cacheFile) | ||
if err != nil { | ||
tok = getTokenFromWeb(config) | ||
saveToken(cacheFile, tok) | ||
} | ||
return config.Client(ctx, tok) | ||
} | ||
|
||
// getTokenFromWeb uses Config to request a Token. | ||
// It returns the retrieved Token. | ||
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token { | ||
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) | ||
fmt.Printf("Go to the following link in your browser then type the "+ | ||
"authorization code: \n%v\n", authURL) | ||
|
||
var code string | ||
if _, err := fmt.Scan(&code); err != nil { | ||
log.Fatalf("Unable to read authorization code: %v", err) | ||
} | ||
|
||
tok, err := config.Exchange(context.Background(), code) | ||
if err != nil { | ||
log.Fatalf("Unable to retrieve token from web: %v", err) | ||
} | ||
return tok | ||
} | ||
|
||
// tokenCacheFile generates credential file path/filename. | ||
// It returns the generated credential path/filename. | ||
func tokenCacheFile() (string, error) { | ||
usr, err := user.Current() | ||
if err != nil { | ||
return "", err | ||
} | ||
tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials") | ||
os.MkdirAll(tokenCacheDir, 0700) | ||
return filepath.Join(tokenCacheDir, | ||
url.QueryEscape("drive-go-quickstart.json")), err | ||
} | ||
|
||
// tokenFromFile retrieves a Token from a given file path. | ||
// It returns the retrieved Token and any read error encountered. | ||
func tokenFromFile(file string) (*oauth2.Token, error) { | ||
f, err := os.Open(file) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer f.Close() | ||
t := &oauth2.Token{} | ||
err = json.NewDecoder(f).Decode(t) | ||
return t, err | ||
} | ||
|
||
// saveToken uses a file path to create a file and store the | ||
// token in it. | ||
func saveToken(file string, token *oauth2.Token) { | ||
fmt.Printf("Saving credential file to: %s\n", file) | ||
f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) | ||
if err != nil { | ||
log.Fatalf("Unable to cache oauth token: %v", err) | ||
} | ||
defer f.Close() | ||
json.NewEncoder(f).Encode(token) | ||
} | ||
|
||
// Authenticate authenticates the application with Google Drive | ||
// server and returns a *drive.Service for further operation. | ||
func Authenticate() *drive.Service { | ||
ctx := context.Background() | ||
|
||
usr, err := user.Current() | ||
if err != nil { | ||
log.Fatalf("Unable to get current user: %v", err) | ||
} | ||
path := usr.HomeDir + "/Documents/client_secret.json" | ||
b, err := ioutil.ReadFile(path) | ||
if err != nil { | ||
log.Fatalf("Unable to read client secret file: %v", err) | ||
} | ||
|
||
config, err := google.ConfigFromJSON(b, drive.DriveScope) | ||
if err != nil { | ||
log.Fatalf("Unable to parse client secret file to config: %v", err) | ||
} | ||
client := getClient(ctx, config) | ||
|
||
srv, err := drive.New(client) | ||
if err != nil { | ||
log.Fatalf("Unable to retrieve drive Client: %v", err) | ||
} | ||
return srv | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"flag" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
"strings" | ||
|
||
A "github.com/KireinaHoro/DriveSync/auth" | ||
C "github.com/KireinaHoro/DriveSync/config" | ||
E "github.com/KireinaHoro/DriveSync/errors" | ||
R "github.com/KireinaHoro/DriveSync/remote" | ||
) | ||
|
||
// initFlags initializes the command-line arguments. | ||
func initFlags() { | ||
flag.Usage = func() { | ||
fmt.Fprintf(os.Stderr, "\nUsage: %s [options] ( <target> || -interactive )\n\n", | ||
filepath.Base(os.Args[0])) | ||
flag.PrintDefaults() | ||
} | ||
|
||
flag.StringVar(&C.ArchiveRootName, "root", "archive", "name of the archive root") | ||
flag.StringVar(&C.Category, "category", "Uncategorized", "destination category") | ||
flag.BoolVar(&C.ForceRecheck, "recheck", true, "force file checksum recheck") | ||
flag.BoolVar(&C.Interactive, "interactive", false, "work interactively") | ||
flag.BoolVar(&C.Verbose, "verbose", false, "verbose output") | ||
flag.BoolVar(&C.CreateMissing, "create-missing", false, "create category if not exist") | ||
|
||
flag.Parse() | ||
|
||
C.Target = flag.Arg(0) | ||
} | ||
|
||
func main() { | ||
initFlags() | ||
|
||
reader := bufio.NewReader(os.Stdin) | ||
|
||
if runtime.GOOS == "darwin" { | ||
// set up proxy for run in restricted network environment | ||
proxyUrl, _ := url.Parse("http://127.0.0.1:8001") | ||
http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(proxyUrl)} | ||
} | ||
|
||
// we need to do this manually for old runtime | ||
if C.Verbose { | ||
fmt.Println("Procs usable:", runtime.NumCPU()) | ||
} | ||
runtime.GOMAXPROCS(runtime.NumCPU()) | ||
|
||
// authenticate to Google Drive server to get *drive.Service | ||
srv := A.Authenticate() | ||
|
||
var ( | ||
info os.FileInfo | ||
err error | ||
) | ||
|
||
if C.Interactive { | ||
fmt.Print("Enter target to sync, in absolute path: ") | ||
C.Target, err = reader.ReadString('\n') | ||
C.Target = strings.TrimRight(C.Target, "\n") | ||
if err != nil { | ||
log.Fatalf("Failed to scan: %v", err) | ||
} | ||
info, err = os.Stat(C.Target) | ||
if err != nil { | ||
log.Fatalf("Failed to stat target '%s': %v", C.Target, err) | ||
} | ||
fmt.Print("Enter desired category: ") | ||
C.Category, err = reader.ReadString('\n') | ||
C.Category = strings.TrimRight(C.Category, "\n") | ||
if err != nil { | ||
log.Fatalf("Failed to scan: %v", err) | ||
} | ||
} else { | ||
if C.Target == "" { | ||
fmt.Fprintln(os.Stderr, "Please specify target properly.") | ||
flag.Usage() | ||
os.Exit(1) | ||
} | ||
if C.Target[0] != '/' { | ||
pwd, err := os.Getwd() | ||
if err != nil { | ||
log.Fatalf("Failed to get current working directory: %v", err) | ||
} | ||
C.Target = filepath.Clean(pwd + "/" + C.Target) | ||
} | ||
info, err = os.Stat(C.Target) | ||
if err != nil { | ||
log.Fatalf("Failed to stat target '%s': %v", C.Target, err) | ||
} | ||
} | ||
if info.IsDir() { | ||
fmt.Printf("Syncing directory '%s'...\n", C.Target) | ||
err = R.SyncDirectory(reader, srv, C.Target, C.Category) | ||
} else { | ||
fmt.Printf("Syncing file '%s'...\n", C.Target) | ||
err = R.SyncFile(reader, srv, C.Target, C.Category) | ||
} | ||
if err != nil { | ||
if _, ok := err.(E.ErrorSetMarkFailed); ok { | ||
log.Printf("Sync succeeded, yet failed to set sync mark: %v", err) | ||
} else { | ||
log.Fatalf("Failed to sync '%s': %v", C.Target, err) | ||
} | ||
} | ||
fmt.Println("Sync succeeded.") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package config | ||
|
||
import ( | ||
U "github.com/KireinaHoro/DriveSync/utils" | ||
) | ||
|
||
var ( | ||
IgnoreList = map[string]struct{}{ | ||
".DS_Store": {}, | ||
".localized": {}, | ||
".idea": {}, | ||
".sync_finished": {}, | ||
} | ||
ArchiveRootID string | ||
CategoryIDs = U.NewSafeMap() | ||
) | ||
|
||
const ( | ||
DriveFolderType = "application/vnd.google-apps.folder" | ||
RetryRatio = 2 | ||
RetryStartingRate = 1 | ||
) | ||
|
||
var ( | ||
ArchiveRootName string // "archive" | ||
Target string // "" | ||
Category string // "Uncategorized" | ||
ForceRecheck bool // true | ||
Interactive bool // false | ||
Verbose bool // false | ||
CreateMissing bool // false | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package errors | ||
|
||
type ErrorNotFound string | ||
|
||
func (r ErrorNotFound) Error() string { | ||
return string(r) | ||
} | ||
|
||
type ErrorAlreadySynced string | ||
|
||
func (r ErrorAlreadySynced) Error() string { | ||
return string(r) | ||
} | ||
|
||
type ErrorChecksumMismatch string | ||
|
||
func (r ErrorChecksumMismatch) Error() string { | ||
return string(r) | ||
} | ||
|
||
type ErrorSetMarkFailed string | ||
|
||
func (r ErrorSetMarkFailed) Error() string { | ||
return string(r) | ||
} | ||
|
Oops, something went wrong.