A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

196 lines
5.4 KiB

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