Add actual file upload to Yandex with progress bar

This commit is contained in:
2019-03-30 07:00:15 +04:00
parent ff258dde2d
commit 1fbeb6c063
17 changed files with 1085 additions and 6 deletions

View File

@@ -12,6 +12,7 @@ import (
const YANDEX_APPID = "7d8a0561fdc44c05bb6695b464403f9c"
const YANDEX_APPPW = "56e12e4ed0d64738bf441a47f68c7146"
const DEVICE_NAME = "yapusher-cli"
const MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024 // 10 gigabytes
var (
c *context.Context
@@ -34,16 +35,23 @@ func New(cc *context.Context) {
Name: "uploadPath",
Description: "Path to upload your file on Yandex.Disk. Must exist before uploading.",
Type: "string",
DefaultValue: "/",
DefaultValue: "",
})
_ = c.Flagger.AddFlag(&flagger.Flag{
Name: "file",
Description: "Path to file that will be uploaded. Max upload size - 50 GB",
Description: "Path to file that will be uploaded. Max upload size - 10 GB",
Type: "string",
DefaultValue: "",
})
_ = c.Flagger.AddFlag(&flagger.Flag{
Name: "force",
Description: "Force file to be uploaded even if destination file on Yandex.Disk already exists.",
Type: "bool",
DefaultValue: false,
})
dlog.Info().Msg("Domain initialized")
}
@@ -56,7 +64,9 @@ func Process() {
filePath, _ := c.Flagger.GetStringValue("file")
if filePath != "" {
uploadFile()
uploadPath, _ := c.Flagger.GetStringValue("uploadPath")
forceUpload, _ := c.Flagger.GetBoolValue("force")
uploadFile(uploadPath, filePath, forceUpload)
}
if !checkAuth() {

View File

@@ -5,13 +5,18 @@ package yandexv1
import (
"encoding/json"
"github.com/schollz/progressbar/v2"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
)
func reportFileProgress(r int64) {
}
func sendCode(code int) {
baseURL := "https://oauth.yandex.ru/token"
@@ -51,11 +56,108 @@ func sendCode(code int) {
dlog.Error().Err(err).Msg("Failed to decode response")
}
dlog.Error().Interface("response", errorData).Msg("Got error from Yandex, not authorized. Please retry authorization")
dlog.Error().Str("error", errorData.Error).Str("description", errorData.ErrorDescription).Msg("Got error from Yandex, not authorized. Please retry authorization")
authorize()
}
os.Exit(0)
}
func uploadFile() {}
func uploadFile(uploadPath string, filePath string, overwriteFile bool) {
uploadRequestURL := "https://cloud-api.yandex.net/v1/disk/resources/upload"
// Checking file existence before requesting
normalizedFilePath, _ := filepath.Abs(filePath)
fileInfo, err := os.Stat(normalizedFilePath)
if err != nil {
if os.IsNotExist(err) {
dlog.Fatal().Err(err).Msg("File for uploading not found")
} else {
dlog.Fatal().Err(err).Msg("Failed to stat uploading file")
}
}
if fileInfo.Size() > (MAX_FILE_SIZE - 1) {
dlog.Fatal().Msg("Requested file is too big")
}
if !fileInfo.Mode().IsRegular() {
dlog.Fatal().Msg("Only regular files uploading is supported right now")
}
client := http.Client{}
uploadInfo := UploadInfo{}
// The first request will get from Yandex upload URL
req, _ := http.NewRequest("GET", uploadRequestURL, nil)
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", "OAuth "+c.Config.Token.AccessToken)
query := url.Values{}
query.Add("path", "disk:/"+uploadPath+"/"+fileInfo.Name())
query.Add("overwrite", strconv.FormatBool(overwriteFile))
req.URL.RawQuery = query.Encode()
resp, err := client.Do(req)
if err != nil {
dlog.Fatal().Err(err).Msg("Failed to send request")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
err = json.NewDecoder(resp.Body).Decode(&uploadInfo)
if err != nil {
dlog.Error().Err(err).Msg("Failed to decode response")
}
} else {
errorData := UploadError{}
err = json.NewDecoder(resp.Body).Decode(&errorData)
if err != nil {
dlog.Error().Err(err).Msg("Failed to decode response")
}
dlog.Info().Str("error", errorData.Error).Str("description", errorData.Description).Msg("Failed to upload file")
os.Exit(1)
}
file, _ := os.Open(normalizedFilePath)
bar := progressbar.NewOptions(
int(fileInfo.Size()),
progressbar.OptionSetBytes(int(fileInfo.Size())),
progressbar.OptionSetRenderBlankState(true),
)
progressReader := &progressReader{
r: file,
progressbar: bar,
}
if uploadInfo.URL == "" {
dlog.Fatal().Msg("Got empty upload URL. Report a bug at https://github.com/fat0troll/yapusher/issues because this situation is impossible.")
}
uploadReq, _ := http.NewRequest("PUT", uploadInfo.URL, progressReader)
uploadResp, err := client.Do(uploadReq)
if err != nil {
dlog.Fatal().Err(err).Msg("Failed to send upload request")
}
defer uploadResp.Body.Close()
switch uploadResp.StatusCode {
case http.StatusCreated:
dlog.Info().Msg("File uploaded successfully")
case http.StatusAccepted:
dlog.Info().Msg("File uploaded successfully, but it will take time for Yandex.Disk to handle it internally. Be patient and don't try to upload single file many times")
case http.StatusRequestEntityTooLarge:
dlog.Fatal().Msg("File upload is too large. Report a bug at https://github.com/fat0troll/yapusher/issues because this situation should be handled before upload attempt.")
case http.StatusInsufficientStorage:
dlog.Fatal().Msg("There is no space left on your Yandex.Disk.")
default:
dlog.Fatal().Msg("Failed to upload file (error on Yandex's side). Try again later.")
}
os.Exit(0)
}

View File

@@ -3,7 +3,46 @@
package yandexv1
import (
"fmt"
"github.com/schollz/progressbar/v2"
"io"
)
type progressReader struct {
r io.Reader
atEOF bool
progressbar *progressbar.ProgressBar
}
func (pr *progressReader) Read(p []byte) (int, error) {
n, err := pr.r.Read(p)
if err == io.EOF {
pr.atEOF = true
}
pr.report(int64(n))
return n, err
}
func (pr *progressReader) report(n int64) {
_ = pr.progressbar.Add64(n)
if pr.atEOF {
fmt.Print("\n\n")
}
}
type TokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
type UploadError struct {
Error string `json:"error"`
Description string `json:"description"`
}
type UploadInfo struct {
URL string `json:"href"`
Method string `json:"method"`
URLIsTemplated bool `json:"templated"`
}