upload file, ext check

This commit is contained in:
Celogeek 2021-12-22 19:06:25 +01:00
parent 359b8a69eb
commit 545282b439
Signed by: celogeek
GPG Key ID: E6B7BDCFC446233A
7 changed files with 187 additions and 65 deletions

View File

@ -10,6 +10,11 @@ import (
"github.com/schollz/progressbar/v3" "github.com/schollz/progressbar/v3"
) )
type FileUploadResult struct {
ImageId int `json:"image_id"`
Url string `json:"url"`
}
func (p *Piwigo) FileExists(md5 string) bool { func (p *Piwigo) FileExists(md5 string) bool {
var resp map[string]*string var resp map[string]*string
@ -22,83 +27,79 @@ func (p *Piwigo) FileExists(md5 string) bool {
return resp[md5] != nil return resp[md5] != nil
} }
func (p *Piwigo) UploadChunks(filename string, nbJobs int) error { func (p *Piwigo) UploadChunks(filename string, nbJobs int) (*FileUploadResult, error) {
md5, err := Md5File(filename) md5, err := Md5File(filename)
if err != nil { if err != nil {
return err return nil, err
} }
if p.FileExists(md5) { if p.FileExists(md5) {
return errors.New("file already exists") return nil, errors.New("file already exists")
} }
st, _ := os.Stat(filename)
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
st, err := f.Stat()
if err != nil {
return err
}
in := make(chan int64)
out := make(chan error)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
chunks, err := Base64Chunker(filename)
errout := make(chan error)
bar := progressbar.DefaultBytes( bar := progressbar.DefaultBytes(
st.Size(), st.Size(),
"uploading", "uploading",
) )
if err != nil {
return nil, err
}
for j := 0; j < nbJobs; j++ { for j := 0; j < nbJobs; j++ {
wg.Add(1) wg.Add(1)
go p.UploadChunk(md5, f, in, out, wg, bar) go p.UploadChunk(md5, chunks, errout, wg, bar)
} }
go func() { go func() {
nbChunks := st.Size()/CHUNK_SIZE + 1
for position := int64(0); position < nbChunks; position++ {
in <- position
}
close(in)
wg.Wait() wg.Wait()
close(out)
bar.Close() bar.Close()
close(errout)
}() }()
var errString string var errstring string
for err := range out { for err := range errout {
errString += err.Error() + "\n" errstring += err.Error() + "\n"
} }
if errString != "" { if errstring != "" {
return errors.New(errString[:len(errString)-1]) return nil, errors.New(errstring)
} }
fmt.Println(md5) var resp *FileUploadResult
err = p.Post("pwg.images.add", &url.Values{
return nil
}
func (p *Piwigo) UploadChunk(md5 string, f *os.File, in chan int64, out chan error, wg *sync.WaitGroup, bar *progressbar.ProgressBar) {
defer wg.Done()
for position := range in {
n, b64, err := Base64Chunk(f, position)
if err != nil {
out <- fmt.Errorf("error on chunk %d: %v", position, err)
continue
}
err = p.Post("pwg.images.addChunk", &url.Values{
"original_sum": []string{md5}, "original_sum": []string{md5},
"position": []string{fmt.Sprint(position)}, "original_filename": []string{filename},
"type": []string{"file"}, "check_uniqueness": []string{"true"},
"data": []string{b64}, }, &resp)
}, nil)
if err != nil { if err != nil {
out <- fmt.Errorf("error on chunk %d: %v", position, err) return nil, err
}
return resp, nil
}
func (p *Piwigo) UploadChunk(md5 string, chunks chan *Base64ChunkResult, errout chan error, wg *sync.WaitGroup, bar *progressbar.ProgressBar) {
defer wg.Done()
for chunk := range chunks {
var err error
data := &url.Values{
"original_sum": []string{md5},
"position": []string{fmt.Sprint(chunk.Position)},
"type": []string{"file"},
"data": []string{chunk.Buffer.String()},
}
for i := 0; i < 3; i++ {
err = p.Post("pwg.images.addChunk", data, nil)
if err == nil {
break
}
}
bar.Add64(chunk.Size)
if err != nil {
errout <- fmt.Errorf("error on chunk %d: %v", chunk.Position, err)
continue continue
} }
bar.Add(n)
} }
} }

View File

@ -1,19 +1,23 @@
package piwigo package piwigo
import ( import (
"bytes"
"crypto/md5" "crypto/md5"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"net/url" "net/url"
"os" "os"
"strings" "strings"
"github.com/schollz/progressbar/v3"
) )
var CHUNK_SIZE int64 = int64(math.Pow(1024, 2)) var CHUNK_BUFF_SIZE int64 = 32 * 1024
var CHUNK_BUFF_COUNT int = 32
var CHUNK_PRECOMPUTE_SIZE int = 8
func DumpResponse(v interface{}) (err error) { func DumpResponse(v interface{}) (err error) {
b, err := json.MarshalIndent(v, "", " ") b, err := json.MarshalIndent(v, "", " ")
@ -40,22 +44,55 @@ func Md5File(filename string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
defer file.Close()
st, _ := file.Stat()
bar := progressbar.DefaultBytes(st.Size(), "checksumming")
hash := md5.New() hash := md5.New()
_, err = io.Copy(hash, file) _, err = io.Copy(io.MultiWriter(hash, bar), file)
if err != nil { if err != nil {
return "", err return "", err
} }
return fmt.Sprintf("%x", hash.Sum(nil)), nil return fmt.Sprintf("%x", hash.Sum(nil)), nil
} }
func Base64Chunk(file *os.File, position int64) (int, string, error) { type Base64ChunkResult struct {
b := make([]byte, CHUNK_SIZE) Position int64
n, err := file.ReadAt(b, position*CHUNK_SIZE) Size int64
if err != nil && err != io.EOF { Buffer bytes.Buffer
return 0, "", err }
}
if n == 0 { func Base64Chunker(filename string) (out chan *Base64ChunkResult, err error) {
return 0, "", errors.New("position out of bound") f, err := os.Open(filename)
} if err != nil {
return n, base64.StdEncoding.EncodeToString(b[:n]), nil return
}
out = make(chan *Base64ChunkResult, CHUNK_PRECOMPUTE_SIZE)
go func() {
b := make([]byte, CHUNK_BUFF_SIZE)
defer f.Close()
defer close(out)
ok := false
for position := int64(0); !ok; position += 1 {
bf := &Base64ChunkResult{
Position: position,
}
b64 := base64.NewEncoder(base64.StdEncoding, &bf.Buffer)
for i := 0; i < CHUNK_BUFF_COUNT; i++ {
n, _ := f.Read(b)
if n == 0 {
ok = true
break
}
bf.Size += int64(n)
b64.Write(b[:n])
}
b64.Close()
out <- bf
}
}()
return
} }

View File

@ -1,14 +1,44 @@
package piwigo package piwigo
import ( import (
"encoding/json"
"errors" "errors"
"net/url" "net/url"
"strings"
) )
type UploadFileType map[string]bool
type StatusResponse struct { type StatusResponse struct {
User string `json:"username"` User string `json:"username"`
Role string `json:"status"` Role string `json:"status"`
Version string `json:"version"` Version string `json:"version"`
Token string `json:"pwg_token"`
UploadFileType UploadFileType `json:"upload_file_types"`
}
func (uft *UploadFileType) UnmarshalJSON(data []byte) error {
var r string
if err := json.Unmarshal(data, &r); err != nil {
return err
}
*uft = UploadFileType{}
for _, v := range strings.Split(r, ",") {
(*uft)[v] = true
}
return nil
}
func (uft UploadFileType) MarshalJSON() ([]byte, error) {
return []byte(`"` + uft.String() + `"`), nil
}
func (uft UploadFileType) String() string {
keys := make([]string, 0, len(uft))
for k, _ := range uft {
keys = append(keys, k)
}
return strings.Join(keys, ",")
} }
func (p *Piwigo) GetStatus() (*StatusResponse, error) { func (p *Piwigo) GetStatus() (*StatusResponse, error) {
@ -22,7 +52,6 @@ func (p *Piwigo) GetStatus() (*StatusResponse, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if resp.User == p.Username { if resp.User == p.Username {
return resp, nil return resp, nil
} }

View File

@ -61,7 +61,7 @@ func (p *Piwigo) Post(method string, form *url.Values, resp interface{}) error {
newBody := &bytes.Buffer{} newBody := &bytes.Buffer{}
tee := io.TeeReader(r.Body, newBody) tee := io.TeeReader(r.Body, newBody)
var RawResult map[string]interface{} var RawResult interface{}
err = json.NewDecoder(tee).Decode(&RawResult) err = json.NewDecoder(tee).Decode(&RawResult)
if err != nil { if err != nil {
return err return err

View File

@ -0,0 +1,36 @@
package piwigo
import (
"fmt"
"net/http"
"net/url"
)
func (p *Piwigo) VideoJSSync(imageId int) error {
Url, err := url.Parse(p.Url)
if err != nil {
return err
}
Url.Path += "/admin.php"
q := Url.Query()
q.Set("page", "plugin")
q.Set("section", "piwigo-videojs/admin/admin_photo.php")
q.Set("sync_metadata", "1")
q.Set("image_id", fmt.Sprint(imageId))
Url.RawQuery = q.Encode()
req, err := http.NewRequest("GET", Url.String(), nil)
if err != nil {
return err
}
if p.Token != "" {
req.AddCookie(&http.Cookie{Name: "pwg_id", Value: p.Token, HttpOnly: true})
}
r, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
return nil
}

View File

@ -1,6 +1,10 @@
package piwigocli package piwigocli
import ( import (
"errors"
"path/filepath"
"strings"
"github.com/celogeek/piwigo-cli/internal/piwigo" "github.com/celogeek/piwigo-cli/internal/piwigo"
) )
@ -15,15 +19,28 @@ func (c *ImagesUploadCommand) Execute(args []string) error {
return err return err
} }
_, err := p.Login() status, err := p.Login()
if err != nil { if err != nil {
return err return err
} }
err = p.UploadChunks(c.Filename, c.NBJobs) ext := strings.ToLower(filepath.Ext(c.Filename)[1:])
if _, ok := status.UploadFileType[ext]; !ok {
return errors.New("unsupported file extension")
}
resp, err := p.UploadChunks(c.Filename, c.NBJobs)
if err != nil { if err != nil {
return err return err
} }
switch ext {
case "ogg", "ogv", "mp4", "m4v", "webm", "webmv":
err = p.VideoJSSync(resp.ImageId)
if err != nil {
return err
}
}
return nil return nil
} }

View File

@ -28,6 +28,8 @@ func (c *SessionStatusCommand) Execute(args []string) error {
{"Version", resp.Version}, {"Version", resp.Version},
{"User", resp.User}, {"User", resp.User},
{"Role", resp.Role}, {"Role", resp.Role},
{"Admin Token", resp.Token},
{"Supported formats", resp.UploadFileType},
}) })
t.SetOutputMirror(os.Stdout) t.SetOutputMirror(os.Stdout)