A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 

265 wiersze
7.4 KiB

  1. package writefreely
  2. import (
  3. "fmt"
  4. "html/template"
  5. "io"
  6. "io/ioutil"
  7. "mime/multipart"
  8. "net/http"
  9. "os"
  10. "path/filepath"
  11. "strings"
  12. "github.com/hashicorp/go-multierror"
  13. "github.com/writeas/impart"
  14. wfimport "github.com/writeas/import"
  15. "github.com/writeas/web-core/log"
  16. )
  17. func viewImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
  18. // Fetch extra user data
  19. p := NewUserPage(app, r, u, "Import", nil)
  20. c, err := app.db.GetCollections(u)
  21. if err != nil {
  22. return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("unable to fetch collections: %v", err)}
  23. }
  24. d := struct {
  25. *UserPage
  26. Collections *[]Collection
  27. Flashes []template.HTML
  28. Message string
  29. InfoMsg bool
  30. }{
  31. UserPage: p,
  32. Collections: c,
  33. Flashes: []template.HTML{},
  34. }
  35. flashes, _ := getSessionFlashes(app, w, r, nil)
  36. for _, flash := range flashes {
  37. if strings.HasPrefix(flash, "SUCCESS: ") {
  38. d.Message = strings.TrimPrefix(flash, "SUCCESS: ")
  39. } else if strings.HasPrefix(flash, "INFO: ") {
  40. d.Message = strings.TrimPrefix(flash, "INFO: ")
  41. d.InfoMsg = true
  42. } else {
  43. d.Flashes = append(d.Flashes, template.HTML(flash))
  44. }
  45. }
  46. showUserPage(w, "import", d)
  47. return nil
  48. }
  49. func handleImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
  50. // limit 10MB per submission
  51. // TODO: increase?
  52. r.ParseMultipartForm(10 << 20)
  53. files := r.MultipartForm.File["files"]
  54. filesSubmitted := len(files)
  55. var filesImported, collsImported int
  56. var errs []error
  57. // TODO: support multiple zip uploads at once
  58. if filesSubmitted == 1 && files[0].Header.Get("Content-Type") == "application/zip" {
  59. filesSubmitted, filesImported, collsImported, errs = importZipPosts(app, w, r, files[0], u)
  60. } else {
  61. filesImported, errs = importFilePosts(app, w, r, files, u)
  62. }
  63. if len(errs) != 0 {
  64. _ = addSessionFlash(app, w, r, multierror.ListFormatFunc(errs), nil)
  65. }
  66. if filesImported == filesSubmitted && filesSubmitted != 0 {
  67. postAdj := "posts"
  68. if filesSubmitted == 1 {
  69. postAdj = "post"
  70. }
  71. if collsImported != 0 {
  72. collAdj := "collections"
  73. if collsImported == 1 {
  74. collAdj = "collection"
  75. }
  76. _ = addSessionFlash(app, w, r, fmt.Sprintf(
  77. "SUCCESS: Import complete, %d %s imported across %d %s.",
  78. filesImported,
  79. postAdj,
  80. collsImported,
  81. collAdj,
  82. ), nil)
  83. } else {
  84. _ = addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: Import complete, %d %s imported.", filesImported, postAdj), nil)
  85. }
  86. } else if filesImported == 0 && filesSubmitted == 0 {
  87. _ = addSessionFlash(app, w, r, "INFO: 0 valid posts found", nil)
  88. } else if filesImported > 0 {
  89. _ = addSessionFlash(app, w, r, fmt.Sprintf("INFO: %d of %d posts imported, see details below.", filesImported, filesSubmitted), nil)
  90. }
  91. return impart.HTTPError{http.StatusFound, "/me/import"}
  92. }
  93. func importFilePosts(app *App, w http.ResponseWriter, r *http.Request, files []*multipart.FileHeader, u *User) (int, []error) {
  94. var fileErrs []error
  95. var count int
  96. for _, formFile := range files {
  97. if filepath.Ext(formFile.Filename) == ".zip" {
  98. fileErrs = append(fileErrs, fmt.Errorf("zips are supported as a single upload only: %s", formFile.Filename))
  99. log.Info("zip included in bulk files, skipping")
  100. continue
  101. }
  102. info, err := formFileToTemp(formFile)
  103. if err != nil {
  104. fileErrs = append(fileErrs, fmt.Errorf("failed to get file info of: %s", formFile.Filename))
  105. log.Error("import textfile: stat temp file: %v", err)
  106. continue
  107. }
  108. post, err := wfimport.FromFile(filepath.Join(os.TempDir(), info.Name()))
  109. if err == wfimport.ErrEmptyFile {
  110. // not a real error so don't log
  111. _ = addSessionFlash(app, w, r, fmt.Sprintf("%s was empty, import skipped", formFile.Filename), nil)
  112. continue
  113. } else if err == wfimport.ErrInvalidContentType {
  114. // same as above
  115. _ = addSessionFlash(app, w, r, fmt.Sprintf("%s is not a supported post file", formFile.Filename), nil)
  116. continue
  117. } else if err != nil {
  118. fileErrs = append(fileErrs, fmt.Errorf("failed to read copy of %s", formFile.Filename))
  119. log.Error("import textfile: file to post: %v", err)
  120. continue
  121. }
  122. post.Collection = r.PostFormValue("collection")
  123. coll, _ := app.db.GetCollection(post.Collection)
  124. if coll == nil {
  125. coll = &Collection{
  126. ID: 0,
  127. }
  128. }
  129. coll.hostName = app.cfg.App.Host
  130. created := post.Created.Format("2006-01-02T15:04:05Z")
  131. submittedPost := SubmittedPost{
  132. Title: &post.Title,
  133. Content: &post.Content,
  134. Font: "norm",
  135. Created: &created,
  136. }
  137. rp, err := app.db.CreatePost(u.ID, coll.ID, &submittedPost)
  138. if err != nil {
  139. fileErrs = append(fileErrs, fmt.Errorf("failed to create post from %s", formFile.Filename))
  140. log.Error("import textfile: create db post: %v", err)
  141. continue
  142. }
  143. // create public post
  144. if coll.ID != 0 && app.cfg.App.Federation {
  145. go federatePost(
  146. app,
  147. &PublicPost{
  148. Post: rp,
  149. Collection: &CollectionObj{
  150. Collection: *coll,
  151. },
  152. },
  153. coll.ID,
  154. false,
  155. )
  156. }
  157. count++
  158. }
  159. return count, fileErrs
  160. }
  161. func importZipPosts(app *App, w http.ResponseWriter, r *http.Request, file *multipart.FileHeader, u *User) (filesSubmitted, importedPosts, importedColls int, errs []error) {
  162. info, err := formFileToTemp(file)
  163. if err != nil {
  164. errs = append(errs, fmt.Errorf("upload temp file: %v", err))
  165. return
  166. }
  167. postMap, err := wfimport.FromZipDirs(filepath.Join(os.TempDir(), info.Name()))
  168. if err != nil {
  169. errs = append(errs, fmt.Errorf("parse posts and collections from zip: %v", err))
  170. return
  171. }
  172. for collKey, posts := range postMap {
  173. if len(posts) == 0 {
  174. continue
  175. }
  176. collObj := CollectionObj{}
  177. if collKey != wfimport.DraftsKey {
  178. coll, err := app.db.GetCollection(collKey)
  179. if err == ErrCollectionNotFound {
  180. coll, err = app.db.CreateCollection(app.cfg, collKey, collKey, u.ID)
  181. if err != nil {
  182. errs = append(errs, fmt.Errorf("create non existent collection: %v", err))
  183. continue
  184. }
  185. coll.hostName = app.cfg.App.Host
  186. collObj.Collection = *coll
  187. } else if err != nil {
  188. errs = append(errs, fmt.Errorf("get collection: %v", err))
  189. continue
  190. }
  191. collObj.Collection = *coll
  192. importedColls++
  193. }
  194. for _, post := range posts {
  195. if post != nil {
  196. filesSubmitted++
  197. created := post.Created.Format("2006-01-02T15:04:05Z")
  198. submittedPost := SubmittedPost{
  199. Title: &post.Title,
  200. Content: &post.Content,
  201. Font: "norm",
  202. Created: &created,
  203. }
  204. rp, err := app.db.CreatePost(u.ID, collObj.Collection.ID, &submittedPost)
  205. if err != nil {
  206. errs = append(errs, fmt.Errorf("create post: %v", err))
  207. continue
  208. }
  209. if collObj.Collection.ID != 0 && app.cfg.App.Federation {
  210. go federatePost(
  211. app,
  212. &PublicPost{
  213. Post: rp,
  214. Collection: &collObj,
  215. },
  216. collObj.Collection.ID,
  217. false,
  218. )
  219. }
  220. importedPosts++
  221. }
  222. }
  223. }
  224. return
  225. }
  226. func formFileToTemp(formFile *multipart.FileHeader) (os.FileInfo, error) {
  227. file, err := formFile.Open()
  228. if err != nil {
  229. return nil, fmt.Errorf("failed to open form file: %s", formFile.Filename)
  230. }
  231. defer file.Close()
  232. tempFile, err := ioutil.TempFile("", fmt.Sprintf("upload-*%s", filepath.Ext(formFile.Filename)))
  233. if err != nil {
  234. return nil, fmt.Errorf("failed to create temporary file for: %s", formFile.Filename)
  235. }
  236. defer tempFile.Close()
  237. _, err = io.Copy(tempFile, file)
  238. if err != nil {
  239. return nil, fmt.Errorf("failed to copy file into temporary location: %s", formFile.Filename)
  240. }
  241. return tempFile.Stat()
  242. }