diff --git a/assets/web/css/styles.css b/assets/web/css/styles.css index 8c0d2c9..6b4a0bf 100644 --- a/assets/web/css/styles.css +++ b/assets/web/css/styles.css @@ -75,3 +75,25 @@ body { grid-template-columns: repeat(5, 1fr); } } + +input[type="file"] { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} + +input[type="file"] + label { + font-size: 1.25em; + font-weight: 700; + color: white; + background-color: black; + display: inline-block; +} + +input[type="file"]:focus + label, +input[type="file"] + label:hover { + background-color: red; +} \ No newline at end of file diff --git a/assets/web/javascript/app.js b/assets/web/javascript/app.js index 5d79fb6..303f05c 100644 --- a/assets/web/javascript/app.js +++ b/assets/web/javascript/app.js @@ -1,6 +1,7 @@ // Register our router, and fire it off initially in case user is being linked a dir. window.addEventListener("hashchange", router, false); router() +let input = "" function getFileListing(provider, path = "") { fetch(`/api/files/${provider}${path}`) @@ -10,6 +11,9 @@ function getFileListing(provider, path = "") { .then((data) => { let files = data["Files"] html` +
+ +
${files.map(file => ` @@ -19,6 +23,9 @@ function getFileListing(provider, path = "") { ).join('')}
` + + input = document.getElementById("file") + input.addEventListener("change", onSelectFile, false) }) } @@ -56,6 +63,20 @@ function router(event = null) { getFileListing(provider, "/" + path) } +function onSelectFile() { + upload(input.getAttribute("data-dir"), input.files[0]) +} +function upload(path, file) { + let formData = new FormData() + formData.append("file", file) + fetch(`/api/files/${path}`, { + method: "POST", + body: formData + }).then(response => response.text()) + .then(text => console.log(text)) + .then(router()) +} + // Tagged template function for parsing a string of text as HTML objects // <3 @innovati for this brilliance. function html(strings, ...things) { diff --git a/files/backblaze.go b/files/backblaze.go index 2fb839c..fa0a0de 100644 --- a/files/backblaze.go +++ b/files/backblaze.go @@ -2,10 +2,12 @@ package files import ( "bytes" + "crypto/sha1" "encoding/json" "fmt" "io" "io/ioutil" + "mime/multipart" "net/http" "strings" ) @@ -44,6 +46,11 @@ type BackblazeBucketInfoPayload struct { Buckets []BackblazeBucketInfo `json:"buckets"` } +type BackblazeUploadInfo struct { + UploadUrl string `json:"uploadUrl"` + AuthToken string `json:"authorizationToken"` +} + // Call Backblaze API endpoint to authorize and gather facts. func (bp *BackblazeProvider) Setup(args map[string]string) bool { applicationKeyId := args["applicationKeyId"] @@ -183,7 +190,66 @@ func (bp *BackblazeProvider) ViewFile(path string, w io.Writer) { } } -func (bp *BackblazeProvider) SaveFile(contents []byte, path string) bool { +func (bp *BackblazeProvider) SaveFile(file multipart.File, handler *multipart.FileHeader, path string) bool { + client := &http.Client{} + bucketIdPayload := fmt.Sprintf(`{"bucketId": "%s"}`, bp.Bucket) + + req, err := http.NewRequest("POST", bp.Location + "/b2api/v2/b2_get_upload_url", + bytes.NewBuffer([]byte(bucketIdPayload))) + if err != nil { + fmt.Println(err.Error()) + return false + } + req.Header.Add("Authorization", bp.Authentication) + + res, err := client.Do(req) + if err != nil { + fmt.Println(err.Error()) + return false + } + bucketData, err := ioutil.ReadAll(res.Body) + if err != nil { + fmt.Println(err.Error()) + return false + } + + var data BackblazeUploadInfo + json.Unmarshal(bucketData, &data) + + req, err = http.NewRequest("POST", + data.UploadUrl, + file, + ) + + if err != nil { + fmt.Println(err.Error()) + return false + } + // Read the content + var bodyBytes []byte + if req.Body != nil { + bodyBytes, _ = ioutil.ReadAll(req.Body) + } + req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + + // Calculate SHA1 and add required headers. + fileSha := sha1.New() + fileSha.Write(bodyBytes) + + req.Header.Add("Authorization", data.AuthToken) + req.Header.Add("X-Bz-File-Name", handler.Filename) + req.Header.Add("Content-Type", "b2/x-auto") + req.Header.Add("X-Bz-Content-Sha1", fmt.Sprintf("%x", fileSha.Sum(nil))) + req.ContentLength = handler.Size + + // Upload in background. + go func() { + res, err = client.Do(req) + if err != nil { + fmt.Println(err.Error()) + } + }() + return true } diff --git a/files/disk.go b/files/disk.go index ce2762d..ab93263 100644 --- a/files/disk.go +++ b/files/disk.go @@ -1,8 +1,10 @@ package files import ( + "fmt" "io" "io/ioutil" + "mime/multipart" "os" "strings" ) @@ -53,11 +55,15 @@ func (dp *DiskProvider) ViewFile(path string, w io.Writer) { } } -func (dp *DiskProvider) SaveFile(contents []byte, path string) bool { - err := ioutil.WriteFile(path, contents, 0600) +func (dp *DiskProvider) SaveFile(file multipart.File, handler *multipart.FileHeader, path string) bool { + f, err := os.OpenFile(dp.Location + path + "/" + handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) if err != nil { + fmt.Println(err.Error()) return false } + defer f.Close() + + io.Copy(f, file) return true } @@ -72,4 +78,4 @@ func (dp *DiskProvider) DetermineType(path string) string { } return "file" -} \ No newline at end of file +} diff --git a/files/fileprovider.go b/files/fileprovider.go index 16a0ff5..1fff12a 100644 --- a/files/fileprovider.go +++ b/files/fileprovider.go @@ -2,6 +2,7 @@ package files import ( "io" + "mime/multipart" ) type FileProvider struct { @@ -32,7 +33,7 @@ type FileProviderInterface interface { Setup(args map[string]string) bool GetDirectory(path string) Directory ViewFile(path string, w io.Writer) - SaveFile(contents []byte, path string) bool + SaveFile(file multipart.File, handler *multipart.FileHeader, path string) bool DetermineType(path string) string } @@ -49,7 +50,7 @@ func (f FileProvider) ViewFile(path string, w io.Writer) { return } -func (f FileProvider) SaveFile(contents []byte, path string) bool { +func (f FileProvider) SaveFile(file multipart.File, handler *multipart.FileHeader, path string) bool { return false } diff --git a/router/filerouter.go b/router/filerouter.go index 8726377..91d6a44 100644 --- a/router/filerouter.go +++ b/router/filerouter.go @@ -2,6 +2,7 @@ package router import ( "encoding/json" + "fmt" "github.com/gmemstr/nas/common" "github.com/gmemstr/nas/files" "github.com/gorilla/mux" @@ -39,6 +40,26 @@ func HandleProvider() common.Handler { } w.Write(data) } + if r.Method == "POST" { + providerCodename := vars["provider"] + providerCodename = strings.Replace(providerCodename, "/", "", -1) + provider := *files.Providers[providerCodename] + err := r.ParseMultipartForm(32 << 20) + if err != nil { + w.Write([]byte("unable to parse form")) + fmt.Println(err.Error()) + return nil + } + file, handler, err := r.FormFile("file") + defer file.Close() + + success := provider.SaveFile(file, handler, vars["file"]) + if !success { + w.Write([]byte("unable to save file")) + return nil + } + w.Write([]byte("saved file")) + } return nil } diff --git a/router/router.go b/router/router.go index 10ee68f..6b3690e 100644 --- a/router/router.go +++ b/router/router.go @@ -56,12 +56,12 @@ func Init() *mux.Router { r.Handle(`/api/files/{provider:[a-zA-Z0-9]+\/*}`, Handle( //auth.RequireAuthorization(1), HandleProvider(), - )).Methods("GET") + )).Methods("GET", "POST") r.Handle(`/api/files/{provider}/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( //auth.RequireAuthorization(1), HandleProvider(), - )).Methods("GET") + )).Methods("GET", "POST") return r }