1package writefreely
2
3import (
4	"encoding/json"
5	"fmt"
6	"html/template"
7	"io"
8	"io/ioutil"
9	"net/http"
10	"os"
11	"path/filepath"
12	"strings"
13	"time"
14
15	"github.com/hashicorp/go-multierror"
16	"github.com/writeas/impart"
17	wfimport "github.com/writeas/import"
18	"github.com/writeas/web-core/log"
19)
20
21func viewImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
22	// Fetch extra user data
23	p := NewUserPage(app, r, u, "Import Posts", nil)
24
25	c, err := app.db.GetCollections(u, app.Config().App.Host)
26	if err != nil {
27		return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("unable to fetch collections: %v", err)}
28	}
29
30	d := struct {
31		*UserPage
32		Collections *[]Collection
33		Flashes     []template.HTML
34		Message     string
35		InfoMsg     bool
36	}{
37		UserPage:    p,
38		Collections: c,
39		Flashes:     []template.HTML{},
40	}
41
42	flashes, _ := getSessionFlashes(app, w, r, nil)
43	for _, flash := range flashes {
44		if strings.HasPrefix(flash, "SUCCESS: ") {
45			d.Message = strings.TrimPrefix(flash, "SUCCESS: ")
46		} else if strings.HasPrefix(flash, "INFO: ") {
47			d.Message = strings.TrimPrefix(flash, "INFO: ")
48			d.InfoMsg = true
49		} else {
50			d.Flashes = append(d.Flashes, template.HTML(flash))
51		}
52	}
53
54	showUserPage(w, "import", d)
55	return nil
56}
57
58func handleImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
59	// limit 10MB per submission
60	r.ParseMultipartForm(10 << 20)
61
62	collAlias := r.PostFormValue("collection")
63	coll := &Collection{
64		ID: 0,
65	}
66	var err error
67	if collAlias != "" {
68		coll, err = app.db.GetCollection(collAlias)
69		if err != nil {
70			log.Error("Unable to get collection for import: %s", err)
71			return err
72		}
73		// Only allow uploading to collection if current user is owner
74		if coll.OwnerID != u.ID {
75			err := ErrUnauthorizedGeneral
76			_ = addSessionFlash(app, w, r, err.Message, nil)
77			return err
78		}
79		coll.hostName = app.cfg.App.Host
80	}
81
82	fileDates := make(map[string]int64)
83	err = json.Unmarshal([]byte(r.FormValue("fileDates")), &fileDates)
84	if err != nil {
85		log.Error("invalid form data for file dates: %v", err)
86		return impart.HTTPError{http.StatusBadRequest, "form data for file dates was invalid"}
87	}
88	files := r.MultipartForm.File["files"]
89	var fileErrs []error
90	filesSubmitted := len(files)
91	var filesImported int
92	for _, formFile := range files {
93		fname := ""
94		ok := func() bool {
95			file, err := formFile.Open()
96			if err != nil {
97				fileErrs = append(fileErrs, fmt.Errorf("Unable to read file %s", formFile.Filename))
98				log.Error("import file: open from form: %v", err)
99				return false
100			}
101			defer file.Close()
102
103			tempFile, err := ioutil.TempFile("", "post-upload-*.txt")
104			if err != nil {
105				fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
106				log.Error("import file: create temp file %s: %v", formFile.Filename, err)
107				return false
108			}
109			defer tempFile.Close()
110
111			_, err = io.Copy(tempFile, file)
112			if err != nil {
113				fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
114				log.Error("import file: copy to temp location %s: %v", formFile.Filename, err)
115				return false
116			}
117
118			info, err := tempFile.Stat()
119			if err != nil {
120				fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
121				log.Error("import file: stat temp file %s: %v", formFile.Filename, err)
122				return false
123			}
124			fname = info.Name()
125			return true
126		}()
127		if !ok {
128			continue
129		}
130
131		post, err := wfimport.FromFile(filepath.Join(os.TempDir(), fname))
132		if err == wfimport.ErrEmptyFile {
133			// not a real error so don't log
134			_ = addSessionFlash(app, w, r, fmt.Sprintf("%s was empty, import skipped", formFile.Filename), nil)
135			continue
136		} else if err == wfimport.ErrInvalidContentType {
137			// same as above
138			_ = addSessionFlash(app, w, r, fmt.Sprintf("%s is not a supported post file", formFile.Filename), nil)
139			continue
140		} else if err != nil {
141			fileErrs = append(fileErrs, fmt.Errorf("failed to read copy of %s", formFile.Filename))
142			log.Error("import textfile: file to post: %v", err)
143			continue
144		}
145
146		if collAlias != "" {
147			post.Collection = collAlias
148		}
149		dateTime := time.Unix(fileDates[formFile.Filename], 0)
150		post.Created = &dateTime
151		created := post.Created.Format("2006-01-02T15:04:05Z")
152		submittedPost := SubmittedPost{
153			Title:   &post.Title,
154			Content: &post.Content,
155			Font:    "norm",
156			Created: &created,
157		}
158		rp, err := app.db.CreatePost(u.ID, coll.ID, &submittedPost)
159		if err != nil {
160			fileErrs = append(fileErrs, fmt.Errorf("failed to create post from %s", formFile.Filename))
161			log.Error("import textfile: create db post: %v", err)
162			continue
163		}
164
165		// Federate post, if necessary
166		if app.cfg.App.Federation && coll.ID > 0 {
167			go federatePost(
168				app,
169				&PublicPost{
170					Post: rp,
171					Collection: &CollectionObj{
172						Collection: *coll,
173					},
174				},
175				coll.ID,
176				false,
177			)
178		}
179		filesImported++
180	}
181	if len(fileErrs) != 0 {
182		_ = addSessionFlash(app, w, r, multierror.ListFormatFunc(fileErrs), nil)
183	}
184
185	if filesImported == filesSubmitted {
186		verb := "posts"
187		if filesSubmitted == 1 {
188			verb = "post"
189		}
190		_ = addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: Import complete, %d %s imported.", filesImported, verb), nil)
191	} else if filesImported > 0 {
192		_ = addSessionFlash(app, w, r, fmt.Sprintf("INFO: %d of %d posts imported, see details below.", filesImported, filesSubmitted), nil)
193	}
194	return impart.HTTPError{http.StatusFound, "/me/import"}
195}
196