@@ 5,6 5,7 @@ import (
"fmt"
"io"
"log"
+ "mime/multipart"
"net/http"
"net/url"
"strconv"
@@ 80,18 81,71 @@ func New(c *c.Controller, log *log.Logger, db dber, e3 E3er, hook Hooker, baseUR
}
}
-func (c *Content) upload(ctx context.Context, filename string, file io.Reader, u user.User) (string, error) {
- raw, err := c.e3.Upload(ctx, false, filename, file)
+type fileSet struct {
+ key, url string
+}
+
+func upload(ctx context.Context, e3 E3er, r *http.Request, file multipart.File, header *multipart.FileHeader, baseURL, key string, c chan fileSet, e chan error) {
+ raw, err := e3.Upload(ctx, false, header.Filename, file)
if err != nil {
- return "", err
+ e <- fmt.Errorf("failed to upload file: %w", err)
+ return
}
val, err := url.Parse(raw)
if err != nil {
- return "", err
+ e <- fmt.Errorf("failed to upload file: %w", err)
+ return
+ }
+
+ url := baseURL + "/file" + val.Path
+
+ c <- fileSet{key, url}
+}
+
+func uploadFileSets(e3 E3er, baseURL string, r *http.Request, u user.User) ([]fileSet, error) {
+ _, _, _ = r.FormFile("") // Dummy read, loads internal r.MulitpartForm state.
+
+ if r.MultipartForm == nil {
+ // No work to do.
+ return []fileSet{}, nil
}
- return c.baseURL + "/file" + val.Path, nil
+ var (
+ fs []fileSet
+ ctx = r.Context()
+ l = len(r.MultipartForm.File)
+ c = make(chan fileSet, l)
+ e = make(chan error, l)
+ )
+
+ if l < 1 {
+ // No work to do.
+ return []fileSet{}, nil
+ }
+
+ for key := range r.MultipartForm.File {
+ file, header, err := r.FormFile(key)
+ if err != nil {
+ return []fileSet{}, err
+ }
+ go upload(ctx, e3, r, file, header, baseURL, key, c, e)
+ }
+
+ for {
+ select {
+ case <-ctx.Done():
+ return []fileSet{}, ctx.Err()
+ case err := <-e:
+ return []fileSet{}, err
+ case f := <-c:
+ fs = append(fs, f)
+ if len(fs) == l {
+ // Done.
+ return fs, nil
+ }
+ }
+ }
}
func (c *Content) tree(w http.ResponseWriter, r *http.Request, spaceID, contenttypeID, contentID string) (user.User, space.Space, contenttype.ContentType, content.Content, error) {
@@ 164,36 218,23 @@ func (c *Content) create(w http.ResponseWriter, r *http.Request) {
})
}
- // TODO: Upload concurrently.
- _, _, _ = r.FormFile("") // Dummy read, loads internal r.MulitpartForm state.
- if r.MultipartForm != nil {
- for key := range r.MultipartForm.File {
- file, header, err := r.FormFile(key)
- if err != nil {
- c.Error(w, r, http.StatusInternalServerError, fmt.Errorf("failed to retreive file: %w", err))
- return
- }
-
- url, err := c.upload(r.Context(), header.Filename, file, user)
- if err != nil {
- c.Error(w, r, http.StatusInternalServerError, fmt.Errorf("failed to upload file: %w", err))
- return
- }
-
- parts := strings.Split(key, "-")
- if len(parts) < 2 {
- c.Error(w, r, http.StatusInternalServerError, errors.New("invalid name field for value"))
- return
- }
-
- typ, name := parts[0], parts[1]
-
- params = append(params, db.ContentNewParam{
- Type: typ,
- Name: name,
- Value: url,
- })
+ // Upload files concurrently.
+ fileSets, err := uploadFileSets(c.e3, c.baseURL, r, user)
+ if err != nil {
+ c.Error(w, r, http.StatusInternalServerError, err)
+ return
+ }
+ for _, fileSet := range fileSets {
+ parts := strings.Split(fileSet.key, "-")
+ if len(parts) < 2 {
+ c.Error(w, r, http.StatusInternalServerError, errors.New("invalid name field for value"))
+ return
}
+ params = append(params, db.ContentNewParam{
+ Type: parts[0],
+ Name: parts[1],
+ Value: fileSet.url,
+ })
}
content, err := c.db.ContentNew(r.Context(), user, space, ct, params)
@@ 318,56 359,38 @@ func (c *Content) update(w http.ResponseWriter, r *http.Request) {
})
}
- // TODO: Upload concurrently.
- _, _, _ = r.FormFile("") // Dummy read, loads internal r.MulitpartForm state.
- if r.MultipartForm != nil {
- for key := range r.MultipartForm.File {
- file, header, err := r.FormFile(key)
- if err != nil {
- c.Error(w, r, http.StatusInternalServerError, fmt.Errorf("failed to retreive file: %w", err))
- return
- }
-
- url, err := c.upload(r.Context(), header.Filename, file, user)
- if err != nil {
- c.Error(w, r, http.StatusInternalServerError, fmt.Errorf("failed to upload file: %w", err))
- return
- }
-
- // Check if we're update image value.
- if strings.Contains(key, "value_update_") {
-
- parts := strings.Split(strings.ReplaceAll(key, "value_update_", ""), "-")
- if len(parts) < 2 {
- c.Error(w, r, http.StatusInternalServerError, errors.New("invalid name field for value"))
- return
- }
-
- _, id := parts[0], parts[1]
-
- updateParams = append(updateParams, db.ContentUpdateParam{
- ID: id,
- Type: valuetype.File,
- Value: url,
- })
-
- continue
- }
-
- parts := strings.Split(key, "-")
+ // Upload files concurrently.
+ fileSets, err := uploadFileSets(c.e3, c.baseURL, r, user)
+ if err != nil {
+ c.Error(w, r, http.StatusInternalServerError, err)
+ return
+ }
+ for _, fileSet := range fileSets {
+ // Check if we're update file value.
+ if strings.Contains(fileSet.key, "value_update_") {
+ parts := strings.Split(strings.ReplaceAll(fileSet.key, "value_update_", ""), "-")
if len(parts) < 2 {
c.Error(w, r, http.StatusInternalServerError, errors.New("invalid name field for value"))
return
}
-
- typ, name := parts[0], parts[1]
-
- newParams = append(newParams, db.ContentNewParam{
- Type: typ,
- Name: name,
- Value: url,
+ updateParams = append(updateParams, db.ContentUpdateParam{
+ ID: parts[1], // ID
+ Type: valuetype.File,
+ Value: fileSet.url,
})
+ continue
}
+ // This is a new file value.
+ parts := strings.Split(fileSet.key, "-")
+ if len(parts) < 2 {
+ c.Error(w, r, http.StatusInternalServerError, errors.New("invalid name field for value"))
+ return
+ }
+ newParams = append(newParams, db.ContentNewParam{
+ Type: parts[0],
+ Name: parts[1],
+ Value: fileSet.url,
+ })
}
content, err = c.db.ContentUpdate(r.Context(), user, space, ct, content, newParams, updateParams)