user feedback logic was updated to report if zero posts were found in a zip and form submissions disable the submit button until the form input for files changes again, preventing possible duplicate submissions on large zip uploads. updated to v0.2.1 wfimport to prevent early error returns when an invalid file is present in a zip.
264 lines
7.4 KiB
Go
264 lines
7.4 KiB
Go
package writefreely
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"io/ioutil"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/writeas/impart"
|
|
wfimport "github.com/writeas/import"
|
|
"github.com/writeas/web-core/log"
|
|
)
|
|
|
|
func viewImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
|
// Fetch extra user data
|
|
p := NewUserPage(app, r, u, "Import", nil)
|
|
|
|
c, err := app.db.GetCollections(u)
|
|
if err != nil {
|
|
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("unable to fetch collections: %v", err)}
|
|
}
|
|
|
|
d := struct {
|
|
*UserPage
|
|
Collections *[]Collection
|
|
Flashes []template.HTML
|
|
Message string
|
|
InfoMsg bool
|
|
}{
|
|
UserPage: p,
|
|
Collections: c,
|
|
Flashes: []template.HTML{},
|
|
}
|
|
|
|
flashes, _ := getSessionFlashes(app, w, r, nil)
|
|
for _, flash := range flashes {
|
|
if strings.HasPrefix(flash, "SUCCESS: ") {
|
|
d.Message = strings.TrimPrefix(flash, "SUCCESS: ")
|
|
} else if strings.HasPrefix(flash, "INFO: ") {
|
|
d.Message = strings.TrimPrefix(flash, "INFO: ")
|
|
d.InfoMsg = true
|
|
} else {
|
|
d.Flashes = append(d.Flashes, template.HTML(flash))
|
|
}
|
|
}
|
|
|
|
showUserPage(w, "import", d)
|
|
return nil
|
|
}
|
|
|
|
func handleImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
|
// limit 10MB per submission
|
|
// TODO: increase?
|
|
r.ParseMultipartForm(10 << 20)
|
|
files := r.MultipartForm.File["files"]
|
|
filesSubmitted := len(files)
|
|
var filesImported, collsImported int
|
|
var errs []error
|
|
// TODO: support multiple zip uploads at once
|
|
if filesSubmitted == 1 && files[0].Header.Get("Content-Type") == "application/zip" {
|
|
filesSubmitted, filesImported, collsImported, errs = importZipPosts(app, w, r, files[0], u)
|
|
} else {
|
|
filesImported, errs = importFilePosts(app, w, r, files, u)
|
|
}
|
|
|
|
if len(errs) != 0 {
|
|
_ = addSessionFlash(app, w, r, multierror.ListFormatFunc(errs), nil)
|
|
}
|
|
if filesImported == filesSubmitted && filesSubmitted != 0 {
|
|
postAdj := "posts"
|
|
if filesSubmitted == 1 {
|
|
postAdj = "post"
|
|
}
|
|
if collsImported != 0 {
|
|
collAdj := "collections"
|
|
if collsImported == 1 {
|
|
collAdj = "collection"
|
|
}
|
|
_ = addSessionFlash(app, w, r, fmt.Sprintf(
|
|
"SUCCESS: Import complete, %d %s imported across %d %s.",
|
|
filesImported,
|
|
postAdj,
|
|
collsImported,
|
|
collAdj,
|
|
), nil)
|
|
} else {
|
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: Import complete, %d %s imported.", filesImported, postAdj), nil)
|
|
}
|
|
} else if filesImported == 0 && filesSubmitted == 0 {
|
|
_ = addSessionFlash(app, w, r, "INFO: 0 valid posts found", nil)
|
|
} else if filesImported > 0 {
|
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("INFO: %d of %d posts imported, see details below.", filesImported, filesSubmitted), nil)
|
|
}
|
|
return impart.HTTPError{http.StatusFound, "/me/import"}
|
|
}
|
|
|
|
func importFilePosts(app *App, w http.ResponseWriter, r *http.Request, files []*multipart.FileHeader, u *User) (int, []error) {
|
|
var fileErrs []error
|
|
var count int
|
|
for _, formFile := range files {
|
|
if filepath.Ext(formFile.Filename) == ".zip" {
|
|
fileErrs = append(fileErrs, fmt.Errorf("zips are supported as a single upload only: %s", formFile.Filename))
|
|
log.Info("zip included in bulk files, skipping")
|
|
continue
|
|
}
|
|
info, err := formFileToTemp(formFile)
|
|
if err != nil {
|
|
fileErrs = append(fileErrs, fmt.Errorf("failed to get file info of: %s", formFile.Filename))
|
|
log.Error("import textfile: stat temp file: %v", err)
|
|
continue
|
|
}
|
|
post, err := wfimport.FromFile(filepath.Join(os.TempDir(), info.Name()))
|
|
if err == wfimport.ErrEmptyFile {
|
|
// not a real error so don't log
|
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("%s was empty, import skipped", formFile.Filename), nil)
|
|
continue
|
|
} else if err == wfimport.ErrInvalidContentType {
|
|
// same as above
|
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("%s is not a supported post file", formFile.Filename), nil)
|
|
continue
|
|
} else if err != nil {
|
|
fileErrs = append(fileErrs, fmt.Errorf("failed to read copy of %s", formFile.Filename))
|
|
log.Error("import textfile: file to post: %v", err)
|
|
continue
|
|
}
|
|
|
|
post.Collection = r.PostFormValue("collection")
|
|
coll, _ := app.db.GetCollection(post.Collection)
|
|
if coll == nil {
|
|
coll = &Collection{
|
|
ID: 0,
|
|
}
|
|
}
|
|
coll.hostName = app.cfg.App.Host
|
|
created := post.Created.Format("2006-01-02T15:04:05Z")
|
|
submittedPost := SubmittedPost{
|
|
Title: &post.Title,
|
|
Content: &post.Content,
|
|
Font: "norm",
|
|
Created: &created,
|
|
}
|
|
rp, err := app.db.CreatePost(u.ID, coll.ID, &submittedPost)
|
|
if err != nil {
|
|
fileErrs = append(fileErrs, fmt.Errorf("failed to create post from %s", formFile.Filename))
|
|
log.Error("import textfile: create db post: %v", err)
|
|
continue
|
|
}
|
|
|
|
// create public post
|
|
|
|
if coll.ID != 0 && app.cfg.App.Federation {
|
|
go federatePost(
|
|
app,
|
|
&PublicPost{
|
|
Post: rp,
|
|
Collection: &CollectionObj{
|
|
Collection: *coll,
|
|
},
|
|
},
|
|
coll.ID,
|
|
false,
|
|
)
|
|
}
|
|
count++
|
|
}
|
|
return count, fileErrs
|
|
}
|
|
|
|
func importZipPosts(app *App, w http.ResponseWriter, r *http.Request, file *multipart.FileHeader, u *User) (filesSubmitted, importedPosts, importedColls int, errs []error) {
|
|
info, err := formFileToTemp(file)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("upload temp file: %v", err))
|
|
return
|
|
}
|
|
|
|
postMap, err := wfimport.FromZipDirs(filepath.Join(os.TempDir(), info.Name()))
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("parse posts and collections from zip: %v", err))
|
|
return
|
|
}
|
|
|
|
for collKey, posts := range postMap {
|
|
if len(posts) == 0 {
|
|
continue
|
|
}
|
|
collObj := CollectionObj{}
|
|
if collKey != wfimport.DraftsKey {
|
|
coll, err := app.db.GetCollection(collKey)
|
|
if err == ErrCollectionNotFound {
|
|
coll, err = app.db.CreateCollection(app.cfg, collKey, collKey, u.ID)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("create non existent collection: %v", err))
|
|
continue
|
|
}
|
|
coll.hostName = app.cfg.App.Host
|
|
collObj.Collection = *coll
|
|
} else if err != nil {
|
|
errs = append(errs, fmt.Errorf("get collection: %v", err))
|
|
continue
|
|
}
|
|
collObj.Collection = *coll
|
|
importedColls++
|
|
}
|
|
|
|
for _, post := range posts {
|
|
if post != nil {
|
|
filesSubmitted++
|
|
created := post.Created.Format("2006-01-02T15:04:05Z")
|
|
submittedPost := SubmittedPost{
|
|
Title: &post.Title,
|
|
Content: &post.Content,
|
|
Font: "norm",
|
|
Created: &created,
|
|
}
|
|
rp, err := app.db.CreatePost(u.ID, collObj.Collection.ID, &submittedPost)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("create post: %v", err))
|
|
continue
|
|
}
|
|
|
|
if collObj.Collection.ID != 0 && app.cfg.App.Federation {
|
|
go federatePost(
|
|
app,
|
|
&PublicPost{
|
|
Post: rp,
|
|
Collection: &collObj,
|
|
},
|
|
collObj.Collection.ID,
|
|
false,
|
|
)
|
|
}
|
|
importedPosts++
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func formFileToTemp(formFile *multipart.FileHeader) (os.FileInfo, error) {
|
|
file, err := formFile.Open()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open form file: %s", formFile.Filename)
|
|
}
|
|
defer file.Close()
|
|
|
|
tempFile, err := ioutil.TempFile("", fmt.Sprintf("upload-*%s", filepath.Ext(formFile.Filename)))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create temporary file for: %s", formFile.Filename)
|
|
}
|
|
defer tempFile.Close()
|
|
|
|
_, err = io.Copy(tempFile, file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to copy file into temporary location: %s", formFile.Filename)
|
|
}
|
|
|
|
return tempFile.Stat()
|
|
}
|