mirror of
https://github.com/gmemstr/sliproad.git
synced 2024-09-20 00:21:15 +01:00
Implemented uploading for Backblaze & Disk (v1)
Implemented file uploading for both the disk and Backblaze providers. Also implements a UI element and frontend logic for doing so from the frontend. Disk will write directly to disk, while the Backblaze provider will attempt to stream the file from memory directly to the POST body. This could introduce some problems down the line, so caching to disk then uploading in the background may need to be implemented. It also performs the final upload using a goroutine so the end client can continue on it's merry way. Backblaze proved a bit tricky to do, and considering switching to using a library for managing Backblaze going forward.
This commit is contained in:
parent
9cd7ab75e8
commit
d86ed1233f
|
@ -75,3 +75,25 @@ body {
|
||||||
grid-template-columns: repeat(5, 1fr);
|
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;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
// Register our router, and fire it off initially in case user is being linked a dir.
|
// Register our router, and fire it off initially in case user is being linked a dir.
|
||||||
window.addEventListener("hashchange", router, false);
|
window.addEventListener("hashchange", router, false);
|
||||||
router()
|
router()
|
||||||
|
let input = ""
|
||||||
|
|
||||||
function getFileListing(provider, path = "") {
|
function getFileListing(provider, path = "") {
|
||||||
fetch(`/api/files/${provider}${path}`)
|
fetch(`/api/files/${provider}${path}`)
|
||||||
|
@ -10,6 +11,9 @@ function getFileListing(provider, path = "") {
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
let files = data["Files"]
|
let files = data["Files"]
|
||||||
html`
|
html`
|
||||||
|
<form action="#" method="post">
|
||||||
|
<input type="file" id="file" data-dir="${provider}${path}"><label for="file">Upload</label>
|
||||||
|
</form>
|
||||||
<div class="grid-sm">
|
<div class="grid-sm">
|
||||||
${files.map(file =>
|
${files.map(file =>
|
||||||
`<a href="${!file.IsDirectory ? `/api/files/${provider}${path}/${file.Name}` : `#${provider}/${path !== "" ? path.replace("/","") + "/" : ""}${file.Name}`}">
|
`<a href="${!file.IsDirectory ? `/api/files/${provider}${path}/${file.Name}` : `#${provider}/${path !== "" ? path.replace("/","") + "/" : ""}${file.Name}`}">
|
||||||
|
@ -19,6 +23,9 @@ function getFileListing(provider, path = "") {
|
||||||
).join('')}
|
).join('')}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
|
input = document.getElementById("file")
|
||||||
|
input.addEventListener("change", onSelectFile, false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +63,20 @@ function router(event = null) {
|
||||||
getFileListing(provider, "/" + path)
|
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
|
// Tagged template function for parsing a string of text as HTML objects
|
||||||
// <3 @innovati for this brilliance.
|
// <3 @innovati for this brilliance.
|
||||||
function html(strings, ...things) {
|
function html(strings, ...things) {
|
||||||
|
|
|
@ -2,10 +2,12 @@ package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -44,6 +46,11 @@ type BackblazeBucketInfoPayload struct {
|
||||||
Buckets []BackblazeBucketInfo `json:"buckets"`
|
Buckets []BackblazeBucketInfo `json:"buckets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BackblazeUploadInfo struct {
|
||||||
|
UploadUrl string `json:"uploadUrl"`
|
||||||
|
AuthToken string `json:"authorizationToken"`
|
||||||
|
}
|
||||||
|
|
||||||
// Call Backblaze API endpoint to authorize and gather facts.
|
// Call Backblaze API endpoint to authorize and gather facts.
|
||||||
func (bp *BackblazeProvider) Setup(args map[string]string) bool {
|
func (bp *BackblazeProvider) Setup(args map[string]string) bool {
|
||||||
applicationKeyId := args["applicationKeyId"]
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package files
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -53,11 +55,15 @@ func (dp *DiskProvider) ViewFile(path string, w io.Writer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dp *DiskProvider) SaveFile(contents []byte, path string) bool {
|
func (dp *DiskProvider) SaveFile(file multipart.File, handler *multipart.FileHeader, path string) bool {
|
||||||
err := ioutil.WriteFile(path, contents, 0600)
|
f, err := os.OpenFile(dp.Location + path + "/" + handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
io.Copy(f, file)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileProvider struct {
|
type FileProvider struct {
|
||||||
|
@ -32,7 +33,7 @@ type FileProviderInterface interface {
|
||||||
Setup(args map[string]string) bool
|
Setup(args map[string]string) bool
|
||||||
GetDirectory(path string) Directory
|
GetDirectory(path string) Directory
|
||||||
ViewFile(path string, w io.Writer)
|
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
|
DetermineType(path string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ func (f FileProvider) ViewFile(path string, w io.Writer) {
|
||||||
return
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/gmemstr/nas/common"
|
"github.com/gmemstr/nas/common"
|
||||||
"github.com/gmemstr/nas/files"
|
"github.com/gmemstr/nas/files"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
@ -39,6 +40,26 @@ func HandleProvider() common.Handler {
|
||||||
}
|
}
|
||||||
w.Write(data)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,12 +56,12 @@ func Init() *mux.Router {
|
||||||
r.Handle(`/api/files/{provider:[a-zA-Z0-9]+\/*}`, Handle(
|
r.Handle(`/api/files/{provider:[a-zA-Z0-9]+\/*}`, Handle(
|
||||||
//auth.RequireAuthorization(1),
|
//auth.RequireAuthorization(1),
|
||||||
HandleProvider(),
|
HandleProvider(),
|
||||||
)).Methods("GET")
|
)).Methods("GET", "POST")
|
||||||
|
|
||||||
r.Handle(`/api/files/{provider}/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle(
|
r.Handle(`/api/files/{provider}/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle(
|
||||||
//auth.RequireAuthorization(1),
|
//auth.RequireAuthorization(1),
|
||||||
HandleProvider(),
|
HandleProvider(),
|
||||||
)).Methods("GET")
|
)).Methods("GET", "POST")
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue